// Copyright Epic Games, Inc. All Rights Reserved. #include "MuT/ASTOpConstantResource.h" #include "MuT/CompilerPrivate.h" #include "MuR/Layout.h" #include "MuR/Mesh.h" #include "MuR/ModelPrivate.h" #include "MuR/ImagePrivate.h" #include "MuR/PhysicsBody.h" #include "MuR/Serialisation.h" #include "MuR/Skeleton.h" #include "Containers/Array.h" #include "HAL/PlatformMath.h" #include "HAL/PlatformFileManager.h" #include "Hash/CityHash.h" #include "Misc/AssertionMacros.h" #include "GenericPlatform/GenericPlatformFile.h" #include "Compression/OodleDataCompression.h" #include // Required for 64-bit printf macros namespace mu { /** Proxy class for a temporary resource while compiling. * The resource may be stored in different ways: * - as is, in memory with its own pointer. * - in a compressed buffer * - saved to a disk file compressed or uncompressed. */ template class MUTABLETOOLS_API ResourceProxyTempFile : public TResourceProxy { private: /** Actual resource to store. If the pointer is valid, it wasn't worth dumping to disk or compressing. */ TSharedPtr Resource; /** Temp filename used if it was necessary. */ FString FileName; /** Size of the resource in memory. */ uint32 UncompressedSize = 0; /** Size of the saved file. It may be the size of the resource in memory, or its compressed size. */ uint32 FileSize = 0; /** Valid if the resource was compressed and stored in memory instead of dumped to disk. */ TArray CompressedBuffer; /** Shared context with cache settings and stats. */ FProxyFileContext& Options; /** Prevent concurrent access to a signal resource. */ FCriticalSection Mutex; public: ResourceProxyTempFile(TSharedPtr InResource, FProxyFileContext& InOptions) : Options(InOptions) { if (!InResource) { return; } IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); FOutputMemoryStream Stream(128*1024); FOutputArchive Arch(&Stream); R::Serialise(InResource.Get(), Arch); UncompressedSize = Stream.GetBufferSize(); if (Stream.GetBufferSize() <= Options.MinProxyFileSize) { // Not worth compressing or caching to disk Resource = InResource; } else { // Compress int64 CompressedSize = 0; constexpr bool bEnableCompression = true; if (bEnableCompression) { int64 CompressedBufferSize = FOodleDataCompression::CompressedBufferSizeNeeded(Stream.GetBufferSize()); CompressedBufferSize = FMath::Max(CompressedBufferSize, int64(Stream.GetBufferSize() / 2)); CompressedBuffer.SetNumUninitialized(CompressedBufferSize); CompressedSize = FOodleDataCompression::CompressParallel( CompressedBuffer.GetData(), CompressedBufferSize, Stream.GetBuffer(), Stream.GetBufferSize(), FOodleDataCompression::ECompressor::Kraken, FOodleDataCompression::ECompressionLevel::SuperFast, true // CompressIndependentChunks ); } bool bCompressed = CompressedSize != 0; if (bCompressed && uint64(CompressedSize) <= Options.MinProxyFileSize) { // Keep the compressed data, and don't store to file CompressedBuffer.SetNum(CompressedSize,EAllowShrinking::Yes); } else { // Save FString Prefix = FPlatformProcess::UserTempDir(); uint32 PID = FPlatformProcess::GetCurrentProcessId(); Prefix += FString::Printf(TEXT("mut.temp.%u"), PID); FString FinalTempPath; IFileHandle* ResourceFile = nullptr; uint64 AttemptCount = 0; while (!ResourceFile && AttemptCount < Options.MaxFileCreateAttempts) { uint64 ThisThreadFileIndex = Options.CurrentFileIndex.load(); while (!Options.CurrentFileIndex.compare_exchange_strong(ThisThreadFileIndex, ThisThreadFileIndex + 1)); FinalTempPath = Prefix + FString::Printf(TEXT(".%.16" PRIx64), ThisThreadFileIndex); ResourceFile = PlatformFile.OpenWrite(*FinalTempPath); ++AttemptCount; } if (!ResourceFile) { UE_LOG(LogMutableCore, Error, TEXT("Failed to create temporary file. Disk full?")); check(false); } if (bCompressed) { FileSize = CompressedSize; ResourceFile->Write(CompressedBuffer.GetData(), FileSize); } else { FileSize = UncompressedSize; ResourceFile->Write(Stream.GetBuffer(), FileSize); } CompressedBuffer.SetNum(0, EAllowShrinking::Yes); delete ResourceFile; FileName = FinalTempPath; Options.FilesWritten++; Options.BytesWritten += FileSize; } } } ~ResourceProxyTempFile() { FScopeLock Lock(&Mutex); if (!FileName.IsEmpty()) { // Delete temp file FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*FileName); FileName.Empty(); } } TSharedPtr Get() override { FScopeLock Lock(&Mutex); TSharedPtr Result; if (Resource) { // Cached as is Result = Resource; } else if (!CompressedBuffer.Num() && !FileName.IsEmpty()) { IFileHandle* ResourceFile = FPlatformFileManager::Get().GetPlatformFile().OpenRead(*FileName); check(ResourceFile); CompressedBuffer.SetNumUninitialized(FileSize); ResourceFile->Read(CompressedBuffer.GetData(), FileSize); delete ResourceFile; bool bCompressed = FileSize != UncompressedSize; if (!bCompressed) { FInputMemoryStream stream(CompressedBuffer.GetData(), FileSize); FInputArchive arch(&stream); Result = R::StaticUnserialise(arch); CompressedBuffer.SetNum(0, EAllowShrinking::Yes); } Options.FilesRead++; Options.BytesRead += FileSize; } if (CompressedBuffer.Num()) { // Cached compressed TArray UncompressedBuf; UncompressedBuf.SetNumUninitialized(UncompressedSize); bool bSuccess = FOodleDataCompression::DecompressParallel( UncompressedBuf.GetData(), UncompressedSize, CompressedBuffer.GetData(), CompressedBuffer.Num()); check(bSuccess); if (bSuccess) { FInputMemoryStream stream(UncompressedBuf.GetData(), UncompressedSize); FInputArchive arch(&stream); Result = R::StaticUnserialise(arch); } if (!FileName.IsEmpty()) { CompressedBuffer.SetNum(0, EAllowShrinking::Yes); } } return Result; } }; //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void ASTOpConstantResource::ForEachChild(const TFunctionRef) { } //------------------------------------------------------------------------------------------------- bool ASTOpConstantResource::IsEqual(const ASTOp& OtherUntyped) const { if (OtherUntyped.GetOpType()==GetOpType()) { const ASTOpConstantResource* Other = static_cast(&OtherUntyped); return Type == Other->Type && ValueHash == Other->ValueHash && LoadedValue == Other->LoadedValue && Proxy == Other->Proxy && SourceDataDescriptor == Other->SourceDataDescriptor; } return false; } //------------------------------------------------------------------------------------------------- mu::Ptr ASTOpConstantResource::Clone(MapChildFuncRef) const { Ptr n = new ASTOpConstantResource(); n->Type = Type; n->Proxy = Proxy; n->LoadedValue = LoadedValue; n->ValueHash = ValueHash; n->SourceDataDescriptor = SourceDataDescriptor; return n; } //------------------------------------------------------------------------------------------------- uint64 ASTOpConstantResource::Hash() const { uint64 res = std::hash()(uint64(Type)); hash_combine(res, ValueHash); return res; } namespace { /** Adds a constant mesh data to a program and returns its constant index. */ int32 AddConstantMesh(FProgram& Program, const TSharedPtr& MeshData, FLinkerOptions& Options) { auto AddMeshToProgram = [&Options, &Program](const TSharedPtr& Mesh) { // Use a map-based deduplication int32 MeshIndex = -1; TSharedPtr MeshKey = Mesh; const int32* MeshIndexPtr = Options.MeshConstantMap.Find(MeshKey); if (!MeshIndexPtr) { MeshIndex = Program.ConstantMeshesPermanent.Add(Mesh); Options.MeshConstantMap.Add(Mesh, MeshIndex); } else { MeshIndex = *MeshIndexPtr; } check(MeshIndex >= 0) return Program.ConstantMeshContentIndices.Add(FConstantResourceIndex{(uint32)MeshIndex, 0}); }; // Split generated mesh data in 4 parts. Geometry and Pose and Physiscs and Metadata. // Inidices for a given rom are sorted by content flag value. static_assert(EMeshContentFlags::GeometryData < EMeshContentFlags::PoseData); static_assert(EMeshContentFlags::PoseData < EMeshContentFlags::PhysicsData); static_assert(EMeshContentFlags::PhysicsData < EMeshContentFlags::MetaData); int32 FirstIndex = Program.ConstantMeshContentIndices.Num(); EMeshContentFlags MeshContentFlags = EMeshContentFlags::None; // GeometryMesh { const EMeshCopyFlags GeometryDataCopyFlags = EMeshCopyFlags::WithSurfaces | EMeshCopyFlags::WithVertexBuffers | EMeshCopyFlags::WithIndexBuffers | EMeshCopyFlags::WithLayouts; TSharedPtr MeshGeometryData = MeshData->Clone(GeometryDataCopyFlags); // Copy geometry related additional buffers. for (const TPair& AdditionalBuffer : MeshData->AdditionalBuffers) { const bool bIsGeometryBufferType = AdditionalBuffer.Key == EMeshBufferType::MeshLaplacianData || AdditionalBuffer.Key == EMeshBufferType::MeshLaplacianOffsets || AdditionalBuffer.Key == EMeshBufferType::UniqueVertexMap; if (bIsGeometryBufferType) { MeshGeometryData->AdditionalBuffers.Emplace(AdditionalBuffer); } } MeshGeometryData->MeshIDPrefix = 0; AddMeshToProgram(MeshGeometryData); EnumAddFlags(MeshContentFlags, EMeshContentFlags::GeometryData); } // Pose Mesh { const EMeshCopyFlags PoseDataCopyFlags = EMeshCopyFlags::WithPoses | EMeshCopyFlags::WithBoneMap; TSharedPtr MeshPoseData = MeshData->Clone(PoseDataCopyFlags); for (const TPair& AdditionalBuffer : MeshData->AdditionalBuffers) { const bool bIsPoseBufferType = AdditionalBuffer.Key == EMeshBufferType::SkeletonDeformBinding; if (bIsPoseBufferType) { MeshPoseData->AdditionalBuffers.Emplace(AdditionalBuffer); } } MeshPoseData->MeshIDPrefix = 0; AddMeshToProgram(MeshPoseData); EnumAddFlags(MeshContentFlags, EMeshContentFlags::PoseData); } // PhysicsMeshData { const EMeshCopyFlags PhysicsDataCopyFlags = EMeshCopyFlags::WithAdditionalPhysics | EMeshCopyFlags::WithPhysicsBody; TSharedPtr MeshPhysicsData = MeshData->Clone(PhysicsDataCopyFlags); // Copy components related additional buffers. for (const TPair& AdditionalBuffer : MeshData->AdditionalBuffers) { const bool bIsPhysicsBufferType = AdditionalBuffer.Key == EMeshBufferType::PhysicsBodyDeformBinding || AdditionalBuffer.Key == EMeshBufferType::PhysicsBodyDeformSelection || AdditionalBuffer.Key == EMeshBufferType::PhysicsBodyDeformOffsets; if (bIsPhysicsBufferType) { MeshPhysicsData->AdditionalBuffers.Emplace(AdditionalBuffer); } } MeshPhysicsData->MeshIDPrefix = 0; AddMeshToProgram(MeshPhysicsData); EnumAddFlags(MeshContentFlags, EMeshContentFlags::PhysicsData); } // MetadataMesh Mesh { const EMeshCopyFlags MetadataDataCopyFlags = EMeshCopyFlags::WithSurfaces | EMeshCopyFlags::WithTags | EMeshCopyFlags::WithSkeletonIDs | EMeshCopyFlags::WithStreamedResources; TSharedPtr MeshMetadataData = MeshData->Clone(MetadataDataCopyFlags); // Add a descriptor MeshBufferSet to the metadata part to have formating info. { FMeshBufferSet VertexMeshFormat; const FMeshBufferSet& VertexBufferSet = MeshData->VertexBuffers; VertexMeshFormat.ElementCount = VertexBufferSet.ElementCount; const int32 NumVertexBuffers = VertexBufferSet.Buffers.Num(); VertexMeshFormat.Buffers.SetNum(NumVertexBuffers); for (int32 BufferIndex = 0; BufferIndex < NumVertexBuffers; ++BufferIndex) { VertexMeshFormat.Buffers[BufferIndex].Channels = VertexBufferSet.Buffers[BufferIndex].Channels; VertexMeshFormat.Buffers[BufferIndex].ElementSize = VertexBufferSet.Buffers[BufferIndex].ElementSize; } MeshMetadataData->VertexBuffers = MoveTemp(VertexMeshFormat); EnumAddFlags(MeshMetadataData->VertexBuffers.Flags, EMeshBufferSetFlags::IsDescriptor); } { FMeshBufferSet IndexMeshFormat; const FMeshBufferSet& IndexBufferSet = MeshData->IndexBuffers; IndexMeshFormat.ElementCount = IndexBufferSet.ElementCount; const int32 NumIndexBuffers = IndexBufferSet.Buffers.Num(); IndexMeshFormat.Buffers.SetNum(NumIndexBuffers); for (int32 BufferIndex = 0; BufferIndex < NumIndexBuffers; ++BufferIndex) { IndexMeshFormat.Buffers[BufferIndex].Channels = IndexBufferSet.Buffers[BufferIndex].Channels; IndexMeshFormat.Buffers[BufferIndex].ElementSize = IndexBufferSet.Buffers[BufferIndex].ElementSize; } MeshMetadataData->IndexBuffers = MoveTemp(IndexMeshFormat); EnumAddFlags(MeshMetadataData->IndexBuffers.Flags, EMeshBufferSetFlags::IsDescriptor); } MeshMetadataData->MeshIDPrefix = 0; AddMeshToProgram(MeshMetadataData); EnumAddFlags(MeshContentFlags, EMeshContentFlags::MetaData); } // For now empty meshes are not discarded. A mesh rom index will be used even if empty. check(Program.ConstantMeshContentIndices.Num() - FirstIndex == 4); FMeshContentRange MeshContentRange; FMemory::Memzero(MeshContentRange); MeshContentRange.SetFirstIndex((uint32)FirstIndex); MeshContentRange.SetContentFlags(MeshContentFlags); MeshContentRange.MeshIDPrefix = MeshData->MeshIDPrefix; return Program.ConstantMeshes.Add(MeshContentRange); } /** Adds a constant image data to a program and returns its constant index. */ uint32 AddConstantImage(FProgram& Program, const TSharedPtr& pImage, FLinkerOptions& Options) { MUTABLE_CPUPROFILER_SCOPE(AddConstantImage); check(pImage->GetSizeX() * pImage->GetSizeY() > 0); // Mips to store int32 MipsToStore = 1; int32 FirstLODIndexIndex = Program.ConstantImageLODIndices.Num(); FImageOperator& ImOp = Options.ImageOperator; TSharedPtr pMip; if (!Options.bSeparateImageMips) { pMip = pImage; } else { // We may want the full mipmaps for fragments of images, regardless of the resident mip size, for intermediate operations. // \TODO: Calculate the mip ranges that makes sense to store. int32 MaxMipmaps = FImage::GetMipmapCount(pImage->GetSizeX(), pImage->GetSizeY()); MipsToStore = MaxMipmaps; // Some images cannot be resized or mipmaped bool bCannotBeScaled = pImage->Flags & FImage::IF_CANNOT_BE_SCALED; if (bCannotBeScaled) { // Store only the mips that we have already calculated. We assume we have calculated them correctly. MipsToStore = pImage->GetLODCount(); } if (pImage->GetLODCount() == 1) { pMip = pImage; } else { pMip = ImOp.ExtractMip(pImage.Get(), 0); } } // Temporary uncompressed version of the image, if we need to generate the mips and the source is compressed. TSharedPtr UncompressedMip; EImageFormat UncompressedFormat = GetUncompressedFormat( pMip->GetFormat() ); for (int32 Mip = 0; Mip < MipsToStore; ++Mip) { check(pMip->GetFormat() == pImage->GetFormat()); // Ensure unique at mip level int32 MipIndex = -1; // Use a map-based deduplication only if we are splitting mips. if (Options.bSeparateImageMips) { MUTABLE_CPUPROFILER_SCOPE(Deduplicate); const int32* IndexPtr = Options.ImageConstantMipMap.Find(pMip); if (IndexPtr) { MipIndex = *IndexPtr; } } if (MipIndex<0) { MipIndex = Program.ConstantImageLODsPermanent.Add(pMip); Options.ImageConstantMipMap.Add(pMip, MipIndex); } Program.ConstantImageLODIndices.Add({ uint32(MipIndex), 0 }); // Generate next mip if necessary if (Mip + 1 < MipsToStore) { TSharedPtr NewMip; if (Mip+1 < pImage->GetLODCount()) { // Extract directly from source image NewMip = ImOp.ExtractMip(pImage.Get(), Mip + 1); } else { // Generate from the last mip. if (UncompressedFormat!=pMip->GetFormat()) { int32 Quality = 4; // TODO if (!UncompressedMip) { UncompressedMip = ImOp.ImagePixelFormat(Quality, pMip.Get(), UncompressedFormat); } UncompressedMip = ImOp.ExtractMip(UncompressedMip.Get(), 1); NewMip = ImOp.ImagePixelFormat(Quality, UncompressedMip.Get(), pMip->GetFormat()); } else { NewMip = ImOp.ExtractMip(pMip.Get(), 1); } } check(NewMip); pMip = NewMip; } } FImageLODRange LODRange; LODRange.FirstIndex = FirstLODIndexIndex; LODRange.LODCount = MipsToStore; LODRange.ImageFormat = pImage->GetFormat(); LODRange.ImageSizeX = pImage->GetSizeX(); LODRange.ImageSizeY = pImage->GetSizeY(); uint32 ImageIndex = Program.ConstantImages.Add(LODRange); return ImageIndex; } } //------------------------------------------------------------------------------------------------- void ASTOpConstantResource::Link(FProgram& Program, FLinkerOptions* Options) { MUTABLE_CPUPROFILER_SCOPE(ASTOpConstantResource_Link); if (!linkedAddress && !bLinkedAndNull) { if (Type == EOpType::ME_CONSTANT) { OP::MeshConstantArgs Args; FMemory::Memzero(Args); TSharedPtr MeshValue = StaticCastSharedPtr(GetValue())->Clone(); check(MeshValue); Args.Skeleton = -1; if (TSharedPtr MeshSkeleton = MeshValue->GetSkeleton()) { Args.Skeleton = Program.AddConstant(MeshSkeleton); MeshValue->SetSkeleton(nullptr); } Args.Value = AddConstantMesh(Program, MeshValue, *Options); int32 DataDescIndex = Options->AdditionalData.SourceMeshPerConstant.Add(SourceDataDescriptor); check(DataDescIndex == Args.Value); linkedAddress = (OP::ADDRESS)Program.OpAddress.Num(); Program.OpAddress.Add((uint32)Program.ByteCode.Num()); AppendCode(Program.ByteCode, Type); AppendCode(Program.ByteCode, Args); } else { OP::ResourceConstantArgs Args; FMemory::Memzero(Args); bool bValidData = true; switch (Type) { case EOpType::IM_CONSTANT: { TSharedPtr pTyped = StaticCastSharedPtr(GetValue()); check(pTyped); if (pTyped->GetSizeX() * pTyped->GetSizeY() == 0) { // It's an empty or degenerated image, return a null operation. bValidData = false; } else { Args.value = AddConstantImage( Program, pTyped, *Options); int32 DataDescIndex = Options->AdditionalData.SourceImagePerConstant.Add(SourceDataDescriptor); check(DataDescIndex == Args.value); } break; } case EOpType::LA_CONSTANT: { TSharedPtr pTyped = StaticCastSharedPtr(GetValue()); check(pTyped); Args.value = Program.AddConstant(pTyped); break; } default: check(false); } if (bValidData) { linkedAddress = (OP::ADDRESS)Program.OpAddress.Num(); Program.OpAddress.Add((uint32)Program.ByteCode.Num()); AppendCode(Program.ByteCode, Type); AppendCode(Program.ByteCode, Args); } else { // Null op linkedAddress = 0; bLinkedAndNull = true; } } // Clear stored value to reduce memory usage. LoadedValue = nullptr; Proxy = nullptr; } } //------------------------------------------------------------------------------------------------- FImageDesc ASTOpConstantResource::GetImageDesc(bool, class FGetImageDescContext*) const { FImageDesc Result; if (Type == EOpType::IM_CONSTANT) { // TODO: cache to avoid disk loading TSharedPtr ConstImage = StaticCastSharedPtr(GetValue()); Result.m_format = ConstImage->GetFormat(); Result.m_lods = ConstImage->GetLODCount(); Result.m_size = ConstImage->GetSize(); } else { check(false); } return Result; } //------------------------------------------------------------------------------------------------- void ASTOpConstantResource::GetBlockLayoutSize(uint64 BlockId, int32* BlockX, int32* BlockY, FBlockLayoutSizeCache*) { switch (Type) { case EOpType::LA_CONSTANT: { TSharedPtr pLayout = StaticCastSharedPtr(GetValue()); check(pLayout); if (pLayout) { int relId = pLayout->FindBlock(BlockId); if (relId >= 0) { *BlockX = pLayout->Blocks[relId].Size[0]; *BlockY = pLayout->Blocks[relId].Size[1]; } else { *BlockX = 0; *BlockY = 0; } } break; } default: check(false); } } //------------------------------------------------------------------------------------------------- void ASTOpConstantResource::GetLayoutBlockSize(int32* pBlockX, int32* pBlockY) { switch (Type) { case EOpType::IM_CONSTANT: { // We didn't find any layout. *pBlockX = 0; *pBlockY = 0; break; } default: checkf(false, TEXT("Instruction not supported")); } } //------------------------------------------------------------------------------------------------- bool ASTOpConstantResource::GetNonBlackRect(FImageRect& maskUsage) const { if (Type == EOpType::IM_CONSTANT) { // TODO: cache TSharedPtr pMask = StaticCastSharedPtr(GetValue()); pMask->GetNonBlackRect(maskUsage); return true; } return false; } //------------------------------------------------------------------------------------------------- bool ASTOpConstantResource::IsImagePlainConstant(FVector4f& colour) const { bool res = false; switch (Type) { case EOpType::IM_CONSTANT: { TSharedPtr pImage = StaticCastSharedPtr(GetValue()); if (pImage->GetSizeX() <= 0 || pImage->GetSizeY() <= 0) { res = true; colour = FVector4f(0.0f,0.0f,0.0f,1.0f); } else if (pImage->Flags & FImage::IF_IS_PLAIN_COLOUR_VALID) { if (pImage->Flags & FImage::IF_IS_PLAIN_COLOUR) { res = true; colour = pImage->Sample(FVector2f(0, 0)); } else { res = false; } } else { if (pImage->IsPlainColour(colour)) { res = true; pImage->Flags |= FImage::IF_IS_PLAIN_COLOUR; } pImage->Flags |= FImage::IF_IS_PLAIN_COLOUR_VALID; } break; } default: break; } return res; } //------------------------------------------------------------------------------------------------- ASTOpConstantResource::~ASTOpConstantResource() { } //------------------------------------------------------------------------------------------------- uint64 ASTOpConstantResource::GetValueHash() const { return ValueHash; } //------------------------------------------------------------------------------------------------- TSharedPtr ASTOpConstantResource::GetValue() const { if (LoadedValue) { return LoadedValue; } else { switch (Type) { case EOpType::IM_CONSTANT: { Ptr> typedProxy = static_cast*>(Proxy.get()); TSharedPtr Resource = typedProxy->Get(); return Resource; } default: check(false); break; } } return nullptr; } //------------------------------------------------------------------------------------------------- void ASTOpConstantResource::SetValue(const TSharedPtr& Value, FProxyFileContext* DiskCacheContext) { MUTABLE_CPUPROFILER_SCOPE(ASTOpConstantResource_SetValue); switch (Type) { case EOpType::IM_CONSTANT: { TSharedPtr Resource = StaticCastSharedPtr(Value); FOutputHashStream stream; { MUTABLE_CPUPROFILER_SCOPE(Serialize); FOutputArchive arch(&stream); FImage::Serialise(Resource.Get(), arch); } ValueHash = stream.GetHash(); if (DiskCacheContext) { Proxy = new ResourceProxyTempFile(Resource, *DiskCacheContext); } else { LoadedValue = Resource; } break; } case EOpType::ME_CONSTANT: { TSharedPtr Resource = StaticCastSharedPtr(Value); FOutputHashStream stream; { MUTABLE_CPUPROFILER_SCOPE(Serialize); FOutputArchive arch(&stream); FMesh::Serialise(Resource.Get(), arch); } ValueHash = stream.GetHash(); LoadedValue = Resource; break; } case EOpType::LA_CONSTANT: { TSharedPtr Resource = StaticCastSharedPtr(Value); FOutputHashStream stream; { MUTABLE_CPUPROFILER_SCOPE(Serialize); FOutputArchive arch(&stream); FLayout::Serialise(Resource.Get(), arch); } ValueHash = stream.GetHash(); LoadedValue = Resource; break; } default: LoadedValue = Value; break; } } mu::Ptr ASTOpConstantResource::GetImageSizeExpression() const { if (Type==EOpType::IM_CONSTANT) { Ptr Res = new ImageSizeExpression; Res->type = ImageSizeExpression::ISET_CONSTANT; TSharedPtr Const = StaticCastSharedPtr(GetValue()); Res->size = Const->GetSize(); return Res; } return nullptr; } FSourceDataDescriptor ASTOpConstantResource::GetSourceDataDescriptor(FGetSourceDataDescriptorContext*) const { return SourceDataDescriptor; } ASTOp::EClosedMeshTest ASTOpConstantResource::IsClosedMesh(TMap* Cache) const { if (Cache) { const ASTOp::EClosedMeshTest* Cached = Cache->Find(this); if (Cached) { return *Cached; } } ASTOp::EClosedMeshTest Result = EClosedMeshTest::Unknown; if (Type == EOpType::ME_CONSTANT) { TSharedPtr Mesh = StaticCastSharedPtr(GetValue()); if (Mesh) { if (Mesh->IsClosed()) { Result = EClosedMeshTest::Yes; } else { Result = EClosedMeshTest::No; } } } if (Cache) { Cache->Add(this,Result); } return Result; } }