Download

TSoftPtr from external path on runtime

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.

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

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.

I have new problem, it is not working in package. Any suggestions pleas.