UE5.0.3 FByteBulkData::LockReadOnly Returns NULL in Packaged Build

I’m working on a project where UTexture2D raw data is being accessed at runtime. I’ve searched around on how to do this, and have the correct asset settings applied to the asset in editor (VectorDisplacement, NoMipMaps, SRGB=false).

Whether using FByteBulkData::LockReadOnly() or FByteBulkData::Lock(LOCK_READ_ONLY), the function works as expected when using PIE, but when played in a packaged game, both those functions return a null pointer. Everything up to that point (UTexture2D* is valid, Mips[0] is valid, BulkData* is valid) appears to be fine as well. I’ve also tried force-updating the Compression/SRGB setting before Lock, but that doesn’t work either.

I’m struggling to figure out why this is breaking in packaged builds, any help?

Relevant Code (5.0.3) :

FTexture2DMipMap& TextureMip = TextureAssetPtr->GetPlatformData()->Mips[0];

FByteBulkData* RawImageData = &TextureMip .BulkData;

const uint8* RawByteArray = static_cast<const uint8*>(RawImageData->LockReadOnly());

Following up with a partial resolution and narrowing down of what I think the problem is.

The FByteBulkData class appears to be an alias for two different BulkData classes, where FByteBulkDataOld is used in WITH_EDITOR=1 builds, and FByteBulkData2 for WITH_EDITOR=0 builds (packaged). Whether this is still relevant to the issue or not, I don’t know, but it did add confusion in tracking down info.

In a WITH_EDITOR build, calling FByteBulkData::IsBulkDataLoaded() returns true, however in a packaged build, it returns false, and I’m guessing this is why using FByteBulkData::LockReadOnly(), FByteBulkData::Lock(LOCK_READ_ONLY), or FByteBulkData::GetCopy(void** Dest, bDiscardInternalCopy), all fail to return a valid pointer.

To fix the unloaded BulkData issue, I added the UTexture2D directory to “Additional Non-Asset Directories to copy” in packaging settings. This fixed FByteBulkData::CanLoadFromDisk() to return true, which allowed FByteBulkData::StartAsyncLoading() to work, finally giving me access to the BulkData pointer, and everything seems to mostly work so far.

However, if I look at my log file for the debug .exe, I’m still getting an “Ensure condition failed”

[2022.11.21-00.21.59:581][692]LogOutputDevice: Error: Ensure condition failed: !IsInlined() || ShouldIgnoreInlineDataReloadEnsures() [File:D:\build++UE5\Sync\Engine\Source\Runtime\CoreUObject\Private\Serialization\BulkData2.cpp] [Line: 1592]
Attempting to reload inline BulkData! This operation is not supported by the IoDispatcher and so will eventually stop working. The calling code should be fixed to retain the inline data in memory and re-use it rather than discard it and then try to reload from disk!

Shortly after that log message follows this warning, again mentioning “inlined bulk data”, which I don’t understand.

[2022.11.21-00.22.08:093][692]LogSerialization: Warning: Reloading inlined bulk data directly from disk, this is detrimental to loading performance.

So while I’m now potentially closer to a proper solution, I’m kind of confused.

  1. Is there a method to properly load and access texture-asset BulkData correctly when attempting to load it from a .PAK file? I have access to the UTexture2D asset, the first MipMap level, but haven’t yet found a way to access BulkData from .PAK.

  2. If I am stuck keeping .UASSET files in the /Content/ directory, how do I address the Ensure Error, and “re-use” the memory that is apparently inlined somewhere, rather than reloading from disk? (And if I must reload from disk, is there a way to make sure the file handle is closed or the BulkData unloaded once I’m done?)

Have you found the solution? I’m facing the same issue.

I’m getting the ensure condition error, but also the following error whenever I’m trying to access my texture:

[2022.12.14-22.56.48:825][928]LogFileManager: Error: Requested read of 67108864 bytes when 67108214 bytes remain (file=…/…/…/MyGame/Content/UI/Textures/Map/MyTex.uexp, size=67109024)

Unreal version is 5.1.