Dynamic Textures

Sometimes it's necessary to create a texture at runtime and update its contents. Depending on your particular needs there are a couple different methods: Dynamic UTexture2D at runtime and efficient...

Updated over 4 years ago Edit Page Revisions

Sometimes it's necessary to create a texture at runtime and update its contents. Depending on your particular needs there are a couple different methods:

Dynamic UTexture2D at runtime and efficient updates every frame

Use UTexture2D::CreateTransient() and copy/paste the following function:

NOTE: Currently UE4 doesn't support updating subregions of a texture so you have to update the entire thing every frame or you will end up with garbage in places. In the DX11 runtime the texture creation is done with D3D11_USAGE_DEFAULT not D3D11_USAGE_DYNAMIC.

NOTE: You must add "RHI" and "RenderCore" to PublicDependencyModuleNames in your project's .Build.cs file to build the following function.

NOTE: As of 4.17 (at least) you can actually use UTexture2D::UpdateTextureRegions instead of the function you'll see below.

DynamicTexture = UTexture2D::CreateTransient(SizeX, SizeY);

// Allocate the texture HRI
DynamicTexture->UpdateResource();

// Use this function to update the texture rects you want to change:
// NOTE: There is a method called UpdateTextureRegions in UTexture2D but it is compiled WITH_EDITOR and is not marked as ENGINE_API so it cannot be linked from plugins.

void UpdateTextureRegions(UTexture2D* Texture, int32 MipIndex, uint32 NumRegions, FUpdateTextureRegion2D* Regions, uint32 SrcPitch, uint32 SrcBpp, uint8* SrcData, bool bFreeData)
{
    if (Texture->Resource)
    {
        struct FUpdateTextureRegionsData
        {
            FTexture2DResource* Texture2DResource;
            int32 MipIndex;
            uint32 NumRegions;
            FUpdateTextureRegion2D* Regions;
            uint32 SrcPitch;
            uint32 SrcBpp;
            uint8* SrcData;
        };

        FUpdateTextureRegionsData* RegionData = new FUpdateTextureRegionsData;

        RegionData->Texture2DResource = (FTexture2DResource*)Texture->Resource;
        RegionData->MipIndex = MipIndex;
        RegionData->NumRegions = NumRegions;
        RegionData->Regions = Regions;
        RegionData->SrcPitch = SrcPitch;
        RegionData->SrcBpp = SrcBpp;
        RegionData->SrcData = SrcData;

        ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
            UpdateTextureRegionsData,
            FUpdateTextureRegionsData*, RegionData, RegionData,
            bool, bFreeData, bFreeData,
            {
            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
                        );
                }
            }
            if (bFreeData)
            {
                FMemory::Free(RegionData->Regions);
                FMemory::Free(RegionData->SrcData);
            }
            delete RegionData;
        });
    }
}

Dynamic UTexture2D at runtime and single update

If you need to create a dynamic texture and update it a single time you can use this method. Note: After every call to Lock/Unlock you must call UpdateResource(). This deletes the HRI texture and recreates it so it won't be fast but if done infrequently it is simpler to use.

Texture = UTexture2D::CreateTransient(SizeX, SizeY);
FTexture2DMipMap& Mip = [Texture]->PlatformData->Mips[Level];
void* Data = Mip.BulkData.Lock( LOCK_READ_WRITE );
FMemory::Memcpy( Data, NewData, DataSize );
Mip.BulkData.Unlock( );
Texture->UpdateResource();

Slate

If you are using Slate, or a Slate viewport to render you can use a FSlateTexture2DRHIRef which can be created from the slate function:

FSlateTexture2DRHIRef Texture = MakeShareable(new FSlateTexture2DRHIRef(Width, Height, PF_B8G8R8A8, NULL, TexCreate_Dynamic, bCreateEmptyTexture));