Cabal1996
(Cabal1996)
December 13, 2021, 11:20am
1
Hi, I’m wandering is it possible to get valid soft pointer to asset which been loaded on runtime from external source? I have it valid until garbage collection happened.
this is my code
TSoftObjectPtr<UTexture2D> MyBPLibrary::LoadSoftTexture(FString path)
{
TArray<uint8> inBinaryArray;
FFileHelper::LoadFileToArray(inBinaryArray, *path);
UTexture2D* NewTexture = FImageUtils::ImportBufferAsTexture2D(inBinaryArray);
return TSoftObjectPtr<UTexture2D>(NewTexture);
}
So you want to stop GC from trashing your texture, right?
The most common way is to just store it in your class as a UPROPERTY
. It’s gonna prevent it from being GC’d until you set it to nullptr
.
Alternatively, you can mark it as “never GC’d” by calling an AddToRoot()
. It is however a bad practice (except in edge cases) compared to having the owning system keep the reference alive via a UPROPERTY
.
Cabal1996
(Cabal1996)
December 13, 2021, 11:34am
3
Actually I do want GC to trash it from RAM but still have this soft pointer to load it again when I need it. It is possible I’m trying to invent a wheel. Task is to download bunch of images on begin play, store it on disk and have them as soft pointers to load and unload when it is necessary. I’ve noticed I can have content browser assets as preassigned soft pointers and load them when needed. Now I’m trying to understand how should I treat external assets to get same result. Main concern is shortage of RAM.
Sure, you can load soft pointers on-the-fly (if you saved it to the disk as a valid uasset), it’s as simple as a softPtr.LoadSynchronous()
call: TSoftObjectPtr::LoadSynchronous | Unreal Engine Documentation
You can also load it async: Asynchronous Asset Loading | Unreal Engine Documentation
Cabal1996
(Cabal1996)
December 13, 2021, 4:12pm
5
Thanks for guidance. I’ve got it working. Had to rewrite few standard functions to achieve needed result.
SOLUTION:
required module : “RHI”
#include "Misc/FileHelper.h"
#include "ImageUtils.h"
#include "IImageWrapper.h"
#include "IImageWrapperModule.h"
#include "Engine/AssetManager.h"
#include "RHI.h"
TSoftObjectPtr<UTexture2D> MyBPLibrary::TestAssetSave(FString path)
{
TArray<uint8> inBinaryArray;
FFileHelper::LoadFileToArray(inBinaryArray, *path);
//start reading binary
IImageWrapperModule& ImageWrapperModule = FModuleManager::Get().LoadModuleChecked<IImageWrapperModule>(TEXT("ImageWrapper"));
EImageFormat Format = ImageWrapperModule.DetectImageFormat(inBinaryArray.GetData(), inBinaryArray.GetAllocatedSize());
UTexture2D* NewTexture = NULL;
EPixelFormat PixelFormat = PF_Unknown;
if (Format != EImageFormat::Invalid)
{
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(Format);
int32 BitDepth = 0;
int32 Width = 0;
int32 Height = 0;
if (ImageWrapper->SetCompressed((void*)inBinaryArray.GetData(), inBinaryArray.GetAllocatedSize()))
{
PixelFormat = PF_Unknown;
ERGBFormat RGBFormat = ERGBFormat::Invalid;
BitDepth = ImageWrapper->GetBitDepth();
Width = ImageWrapper->GetWidth();
Height = ImageWrapper->GetHeight();
if (BitDepth == 16)
{
PixelFormat = PF_FloatRGBA;
RGBFormat = ERGBFormat::BGRA;
}
else if (BitDepth == 8)
{
PixelFormat = PF_B8G8R8A8;
RGBFormat = ERGBFormat::BGRA;
}
else
{
return false;
}
TArray64<uint8> UncompressedData;
ImageWrapper->GetRaw(RGBFormat, BitDepth, UncompressedData);
//NewTexture = UTexture2D::CreateTransient(Width, Height, PixelFormat);
FString PackageName = TEXT("/Game/ProceduralTextures/");
FString TextureName = TEXT("NewTexture");
PackageName += TextureName;
UPackage* Package = CreatePackage(nullptr, *PackageName);
Package->FullyLoad();
LLM_SCOPE(ELLMTag::Textures);
if (Width > 0 && Height > 0 &&
(Width % GPixelFormats[PixelFormat].BlockSizeX) == 0 &&
(Height % GPixelFormats[PixelFormat].BlockSizeY) == 0)
{
EObjectFlags obflags = RF_Public | RF_Standalone;// | RF_MarkAsRootSet;
NewTexture = NewObject<UTexture2D>(
Package,
*TextureName,
obflags
);
NewTexture->PlatformData = new FTexturePlatformData();
NewTexture->PlatformData->SizeX = Width;
NewTexture->PlatformData->SizeY = Height;
NewTexture->PlatformData->PixelFormat = PixelFormat;
// Allocate first mipmap.
int32 NumBlocksX = Width / GPixelFormats[PixelFormat].BlockSizeX;
int32 NumBlocksY = Height / GPixelFormats[PixelFormat].BlockSizeY;
FTexture2DMipMap* Mip = new FTexture2DMipMap();
NewTexture->PlatformData->Mips.Add(Mip);
Mip->SizeX = Width;
Mip->SizeY = Height;
Mip->BulkData.Lock(LOCK_READ_WRITE);
Mip->BulkData.Realloc(NumBlocksX * NumBlocksY * GPixelFormats[PixelFormat].BlockBytes);
Mip->BulkData.Unlock();
}
else
{
UE_LOG(LogTexture, Warning, TEXT("Invalid parameters specified for UTexture2D::CreateTransient()"));
}
if (NewTexture)
{
NewTexture->bNotOfflineProcessed = true;
uint8* MipData = static_cast<uint8*>(NewTexture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE));
// Bulk data was already allocated for the correct size when we called CreateTransient above
FMemory::Memcpy(MipData, UncompressedData.GetData(), NewTexture->PlatformData->Mips[0].BulkData.GetBulkDataSize());
//assembling texture from source file
NewTexture->PlatformData->Mips[0].BulkData.Unlock();
//NewTexture->Source.Init(Width, Height, 1, 1, ETextureSourceFormat::TSF_BGRA8, UncompressedData.GetData()); // cause carshes
NewTexture->UpdateResource();
Package->MarkPackageDirty();
FAssetRegistryModule::AssetCreated(NewTexture);
FString PackageFileName = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension());
bool bSaved = UPackage::SavePackage(Package, NewTexture, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, *PackageFileName, GError, nullptr, true, true, SAVE_NoError);
}
}
}
return TSoftObjectPtr<UTexture2D>(NewTexture);
}
Credit to this post UE4 - Save a procedurally generated texture as a new asset - Isara Tech.
Cabal1996
(Cabal1996)
December 13, 2021, 4:59pm
6
I have new problem, it is not working in package. Any suggestions pleas.