Setting Up a Dynamic Texture

I found this example code for a dynamic texture here: , and I’ve been trying to set up a simple class derived from Actor that fills a 2D texture with data that I can create in C++. I’ve got it compiling and running, but my texture always comes out white (I have it hard-coded where it should all be blue). I’m not sure what I’m missing.

Header:



#pragma once

#include "GameFramework/Actor.h"
#include "EchoManager.generated.h"

#define ECHO_TEXTURE_SIZEX 1024
#define ECHO_TEXTURE_SIZEY 1024
#define ECHO_TEXTURE_DATA_LENGTH ECHO_TEXTURE_SIZEX * ECHO_TEXTURE_SIZEY * 4

UCLASS()
class ECHOES_API AEchoManager : public AActor
{
	GENERATED_UCLASS_BODY()

protected:

	/** Dynamically created texture */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Echo)
	UTexture2D* EchoTexture;

	/** Update texture region from  */
	void UpdateTextureRegions(UTexture2D* Texture, int32 MipIndex, uint32 NumRegions, FUpdateTextureRegion2D* Regions, uint32 SrcPitch, uint32 SrcBpp, uint8* SrcData, bool bFreeData);
	
	UFUNCTION(BlueprintCallable, Category = Echo)
	void CreateEchoTexture();

	UFUNCTION(BlueprintCallable, Category = Echo)
	void UpdateEcho();

private:

	UPROPERTY()
	TArray<FColor> data;

	FUpdateTextureRegion2D *echoUpdateTextureRegion;
};


Body:




#include "Echoes.h"
#include "EchoManager.h"


AEchoManager::AEchoManager(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{

	PrimaryActorTick.bCanEverTick = true;

	echoUpdateTextureRegion = new FUpdateTextureRegion2D(0, 0, 0, 0, ECHO_TEXTURE_SIZEX, ECHO_TEXTURE_SIZEY);
}

//////////////////////////////////////////////////////////////////////////
// Texture

void AEchoManager::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;
			});
	}
}


void AEchoManager::CreateEchoTexture()
{
		
	// Create echo texture
	EchoTexture = UTexture2D::CreateTransient(1024, 1024);

	for (int i = 0; i < ECHO_TEXTURE_DATA_LENGTH; ++i)
	{

		data.Add(FColor(0,0,255,255));
	}

	UpdateTextureRegions(EchoTexture, (int32)0, (uint32)1, echoUpdateTextureRegion, (uint32)(4 * ECHO_TEXTURE_SIZEX), (uint32)4, (uint8*)data.GetTypedData(), false);

	EchoTexture->UpdateResource();
	

	UE_LOG(LogTemp, Warning, TEXT("Create Echo Texture."));
}

void AEchoManager::UpdateEcho()
{

	//UpdateTextureRegions(EchoTexture, (int32)0, (uint32)1, &echoUpdateTextureRegion, (uint32)(4 * ECHO_TEXTURE_SIZEX), (uint32)4, data, false);
	//EchoTexture->UpdateResource();

	//UE_LOG(LogTemp, Warning, TEXT("Update Echo Texture."));
}



And a simple blueprint set up simply to call the Create and Update functions. Right now the Update function is empty, but I eventually want to add some code to change the texture data and update it dynamically every frame.

I’m not an expert in C++ or the inner workings of UE4 so there may be something obvious I’ve overlooked. A working example of a dynamic texture or any suggestions on how to do it better would be greatly appreciated. Thanks.

One thing that immediately stands out is that you’re setting the texture on the material on tick, which is unnecessary. The render command will update the texture itself - as long as it’s been set to the material at some point, the material will show the texture changes. You can just move it to the begin play - as long as the texture reference itself is valid, it should work to set it on the material then.

Oh, and perhaps this:



#define ECHO_TEXTURE_SIZEX 1024
#define ECHO_TEXTURE_SIZEY 1024
#define ECHO_TEXTURE_DATA_LENGTH ECHO_TEXTURE_SIZEX * ECHO_TEXTURE_SIZEY * 4




	for (int i = 0; i < ECHO_TEXTURE_DATA_LENGTH; ++i)
	{

		data.Add(FColor(0,0,255,255));
	}


An FColor is 4 bytes, so you don’t need 1024 * 1024 * 4 of them, just 1024 * 1024.

Change this



#define ECHO_TEXTURE_DATA_LENGTH ECHO_TEXTURE_SIZEX * ECHO_TEXTURE_SIZEY * 4


to this



#define ECHO_TEXTURE_DATA_LENGTH ECHO_TEXTURE_SIZEX * ECHO_TEXTURE_SIZEY


If you had a pure uint8 array, you’d want to keep the * 4.

Yeah, that’s right. I switched that.

Yeah, the explanation for that is that I had originally been using a uint8 array, but switched to FColor without updating that define. I saw an example on the forums of someone using FColor for this and it seemed to be more convenient than uint8. The texture still seems to be coming out a very bright grey colour.

I tried using:


data.Init(FColor(0, 0, 255, 255), ECHO_TEXTURE_DATA_LENGTH);

to initialize the array to all the same color, but it’s the same thing. The problem is that nothing I do after CreateTransient() seems to have any effect on the texture at all. Which is strange because several people seem to be using that function from the wiki to create dynamic textures, but I must be getting one of the parameters wrong somehow.

I think I figured it out so I was just going to post a follow-up for reference for anyone else who runs into this.

There were two problems:
A) I wasn’t calling “EchoTexture->UpdateResource()” before updating the texture regions so the texture didn’t exist
B) The texture resources aren’t initialized on BeginPlay() so it won’t work until it is updated later by a tick

Anyway, I thought I would upload the minimal working example for reference:

Header:



#pragma once

#include "GameFramework/Actor.h"
#include "EchoManager.generated.h"

#define ECHO_TEXTURE_SIZEX 1024
#define ECHO_TEXTURE_SIZEY 1024
#define ECHO_TEXTURE_DATA_LENGTH ECHO_TEXTURE_SIZEX * ECHO_TEXTURE_SIZEY

UCLASS()
class ECHOES_API AEchoManager : public AActor
{
	GENERATED_UCLASS_BODY()

protected:

	/** Dynamically created texture for the echo wave */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Echo)
	UTexture2D* EchoTexture;

	/** Update texture region from  */
	void UpdateTextureRegions(UTexture2D* Texture, int32 MipIndex, uint32 NumRegions, FUpdateTextureRegion2D* Regions, uint32 SrcPitch, uint32 SrcBpp, uint8* SrcData, bool bFreeData);
	
	UFUNCTION(BlueprintCallable, Category = Echo)
	void CreateEchoTexture();

	UFUNCTION(BlueprintCallable, Category = Echo)
	void UpdateEcho();

private:

	UPROPERTY()
	TArray<FColor> data;

	FUpdateTextureRegion2D *echoUpdateTextureRegion;
};



Body:



#include "Echoes.h"
#include "EchoManager.h"


AEchoManager::AEchoManager(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
	PrimaryActorTick.bCanEverTick = true;
	echoUpdateTextureRegion = new FUpdateTextureRegion2D(0, 0, 0, 0, ECHO_TEXTURE_SIZEX, ECHO_TEXTURE_SIZEY);  // Note: This never gets freed
	
}

//////////////////////////////////////////////////////////////////////////
// Texture

void AEchoManager::UpdateTextureRegions(UTexture2D* Texture, int32 MipIndex, uint32 NumRegions, FUpdateTextureRegion2D* Regions, uint32 SrcPitch, uint32 SrcBpp, uint8* SrcData, bool bFreeData)
{
	if (Texture && 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;
			});
	}
}


void AEchoManager::CreateEchoTexture()
{		
	// Create echo texture
	EchoTexture = UTexture2D::CreateTransient(ECHO_TEXTURE_SIZEX, ECHO_TEXTURE_SIZEY);
	EchoTexture->UpdateResource();

	// Initialize data
	data.Init(FColor(0, 0, 255, 255), ECHO_TEXTURE_DATA_LENGTH);	
}

void AEchoManager::UpdateEcho()
{
	UpdateTextureRegions(EchoTexture, (int32)0, (uint32)1, echoUpdateTextureRegion, (uint32)(4 * ECHO_TEXTURE_SIZEX), (uint32)4, (uint8*)data.GetTypedData(), false);	
}


And the “echo” word I used was going to use the dynamic texture for an experimental effect that visualizes sound.

I’ve tried implementing this, and I’m getting a lot of unresolved external symbol errors on the updatetextureregions function. Does your implementation still work or has some portion of that function changed?

I read on an answer hub post that you have to add “RHI” and “RenderCore” to the dependency modules in your projects build.cs file.
on a fresh project it should look like this:

PublicDependencyModuleNames.AddRange(new string] { "Core", "CoreUObject", "Engine", "InputCore", "RHI", "RenderCore" });

Which got it to build for me but I still have some other issue preventing the texture to appear correctly.

EDIT: actually I believe if you use the “minimal working” code above and add “RHI” and “RenderCore” to your dependency module names it should work. The issue I was having was related to something else. This is as of UE4 4.9.2

Hi, on a related issue: I need to create a new dynamic texture several times per second. Each time I create a new transient texture and call UpdateResource() though the size of the texture streaming pool keeps increasing, even if the texture is not used anywhere and even forcing the garbage collection.
Is this expected? Since I always need only the last one, how can I delete the past dynamic textures?

Use ConditionalBeginDestroy() on UTexture object.

Were you able to find a solution to this ?
I am having the same issue and using ConditionalBeginDestroy() on UTexture object did not work.

In terms of memory allocation that’s going to suck for performance. It’s faster to clear and re-use existing textures than it is to create new ones. Can you not just use a cycling pool of textures?

ConditionalBeginDestroy() (as it’s name suggests) is conditional. It won’t delete the object if it’s still referenced somewhere, which I’m willing to bet it probably still is.

Any update on this particular problem? Pulling my hair thinking how to solve this.