Download

Splitting a UTexture2D at runtime

I’m trying to split a UTexture2D into multiple parts at runtime. I’m basically wanting to create an array of textures that I can use in my UI later. Think of an inventory item that takes up 4 slots. I want to use a single texture and divide it accordingly into individual textures.

Is this possible?

Yeah it’s possible, we create dynamic images for all sorts of purposes, HUD and materials.

Essentially during init phase create FUpdateTextureRegion2D, create UTexture2D via CreateTransient(), and then update its contents. See below for code snippets. I haven’t read pixels of another UTexture2D, but most likely trivial (my code sample doesn’t cover reading pixels of supplied UTexture2D)

I’d quesiton this workflow approach, because it may be simpler to actually have individual textures as assets, and collage them as required by HUD elements.

/// in header file
#include "Engine/Texture2D.h"
///... declare members, it's important that FUpdateTextureRegion2D lives on main thread for 
/// as long as this UTexture2D is in use.  Call FMemory::Free(UpdateTextureRegion) in destructor 
/// or some other adequate time.  SrcData contents are not used after RENDER_COMMAND 
/// completes, but your code executes on main thread, so either don't free them at all, or free 
/// them after next tick.
UTexture2D* Image;
FUdateTextureRegion2D* UpdateTextureRegion
TArray<uint8> SrcData;

/// in implementation

int32 Size = 256;
UpdateTextureRegion = new FUpdateTextureRegion2D(0, 0, 0, 0, Size, Size);
Image = UTexture2D::CreateTransient(Size, Size);
Image->NeverStream = true;
Image->UpdateResource();
///... presumably populate SrcData with pixel values you desire, byte order is 
/// Blue, Green, Red, Alpha, then repeat for next pixel along x axis, and then along y
struct FUpdateTextureRegionsData
{
	FTexture2DResource* Texture2DResource;
	int32 MipIndex;
	uint32 NumRegions;
	FUpdateTextureRegion2D* Regions;
	uint32 SrcPitch;
	uint32 SrcBpp;
	uint8* SrcData;
};

FUpdateTextureRegionsData* RegionData = new FUpdateTextureRegionsData;
RegionData->Texture2DResource = (FTexture2DResource*)Image->Resource;
RegionData->MipIndex = 0;
RegionData->NumRegions = 1;
RegionData->Regions = UpdateTextureRegion;
RegionData->SrcPitch = Size * 4;
RegionData->SrcBpp = 4;
RegionData->SrcData = SrcData.GetData();

ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(
	UpdateTextureRegionsData,
	FUpdateTextureRegionsData*, RegionData, RegionData,
	{
		for (uint32 RegionIndex = 0; RegionIndex < RegionData->NumRegions; ++RegionIndex)
		{
			int32 CurrentFirstMip = RegionData->Texture2DResource->GetCurrentFirstMip();
			if (RegionData->MipIndex >= CurrentFirstMip)
			{
				RHIUpdateTexture2D(
					RegionData->Texture2DResource->GetTexture2DRHI(),
					RegionData->MipIndex - CurrentFirstMip,
					RegionData->Regions[RegionIndex],
					RegionData->SrcPitch,
					RegionData->SrcData
					+ RegionData->Regions[RegionIndex].SrcY * RegionData->SrcPitch
					+ RegionData->Regions[RegionIndex].SrcX * RegionData->SrcBpp
					);
			}
		}
		delete RegionData;
	});

Thank you for the reply and code snippets. At first glance this looks like it will get me going in the right direction. Looks like the heavy lifting is being done in the FUpdateTextureRegionsData() and UTexture2D::CreateTransient(). I’ll start experimenting.

As for rethinking my workflow - I’m on the fence. In my case, I will have ~50 items that will range in size from 1 to 8 slots. Being able to change the size of an item down the road without having to manually segment my image/texture seems to be worth the effort right now, but it is something I am considering.

Thanks again.

1 Like