C++ - Updating texture data

Hi guys,

we’re currently updating a texture every frame and I was wondering if there was a faster form of this bit of code here:

			void* TextureData = newtex->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
			FMemory::Memcpy(TextureData, uncompressedBGRA->GetData(), uncompressedBGRA->Num());
			newtex->PlatformData->Mips[0].BulkData.Unlock();

			
			// Update the rendering resource from data.
			newtex->UpdateResource();
			//Put window texture in here
			ChangeTexture(newtex);

We’re running this bit of code several times a frame (depending on how many textures we need to update) and I’m curious as to whether or not:

a) moved to another thread (from what I understand, updating UObjects is not to be done outside of the main rendering thread

b) whether there’s a faster / more efficient form of this code. Basically assigning an array of bytes to a texture.

Thanks,
-Paul

I would setup a texture like this:

// create dynamic texture
m_pDynamicTexture = UTexture2D::CreateTransient(TEX_WIDTH, TEX_HEIGHT, TEX_PIXEL_FORMAT);
m_pDynamicTexture->UpdateResource();

m_pDynamicTexture->AddressX = TEX_ADDRESS_X;
m_pDynamicTexture->AddressY = TEX_ADDRESS_Y;
m_pDynamicTexture->Filter = TEX_FILTER;
m_pDynamicTexture->RefreshSamplerStates();

setup with a dynamic material instance using UMaterialInstanceDynamic::SetTextureParameterValue,
then update it like this:

ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
	UpdateDynamicTextureCode,
	UTexture2D*, pTexture, m_pDynamicTexture,
	const uint8*, pData, pData,
	{
		FUpdateTextureRegion2D region;
region.SrcX = 0;
region.SrcY = 0;
region.DestX = 0;
region.DestY = 0;
region.Width = TEX_WIDTH;
region.Height = TEX_HEIGHT;

FTexture2DResource* resource = (FTexture2DResource*)pTexture->Resource;
RHIUpdateTexture2D(resource->GetTexture2DRHI(), 0, region, region.Width * TEX_PIXEL_SIZE_IN_BYTES, pData);
	});

Hope it helps!

This looks solid, though I have a few questions:

  1. What is TEX_ADDRESS_X, TEX_ADDRESS_Y ? I’m already using a dynamic texture and it’s assigned though im not using these parameters.
  2. Where does ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER go, and how does it reference m_pDynamicTexture ? It looks to me as if you’re redefining m_pDynamicTexture in ENQUEUE; am I reading it wrong? Also, can this macro be called from a FRunnable thread?
  3. What is UpdateDynamicTextureCode ?

Sorry if I seem lost. I’ll be anxiously awaiting your reply and will be researching myself in the meantime. Maybe I’ll be able to figure it out :slight_smile:

I believe those are simply macros for a TextureAddress TEnumAsByte value. If I understand correctly these are things like Clamp, Wrap, Mirror, etc…

  1. The address mode tells how the textures is sampled outside 0…1 texture coordinate range. The valid values are TA_WRAP, TA_CLAMP & TA_MIRROR. If you’re not familiar with texture addressing don’t bother with them. I bet the default values will work just fine.

2 & 3) Add the ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER into your tick function. It will schedule a command to run on the rendering thread for avoiding CPU/GPU synchronizations. Because of this, you can’t use the same pData for each frame (the rendering thread might lag behind).

It’s best to do something like:

YourClass:Tick(float deltaTime)
{
	uint16* pData = new uint16[pitch * h * 3];
	//*** FILL pData HERE ***//

	ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
		UpdateDynamicTextureCode,
		UTexture2D*, pTexture, m_pDynamicTexture,
		const uint8*, pData, pData,
		{
			FUpdateTextureRegion2D region;
		region.SrcX = 0;
		region.SrcY = 0;
		region.DestX = 0;
		region.DestY = 0;
		region.Width = TEX_WIDTH;
		region.Height = TEX_HEIGHT;

		FTexture2DResource* resource = (FTexture2DResource*)pTexture->Resource;
		RHIUpdateTexture2D(resource->GetTexture2DRHI(), 0, region, region.Width * TEX_PIXEL_SIZE_IN_BYTES, pData);
		delete[] pData;
		});
}

TEXTURE_PIXEL_SIZE_IN_BYTES depends on your pixel format (ex: use 4 for R8G8B8A8 pixel format).

No :slight_smile:

TEXTURE_PIXEL_SIZE_IN_BYTES depends on your pixel format (ex: use 4 for PF_R8G8B8A8 pixel format, use 2 for PF_R8G8, …).

So ive been playing around and I figured out 2) and 3)… it seems that I can’t reference variables from within the macro so how do I obtain TEXTURE_PIXEL_SIZE_IN_BYTES ?

would TEXTURE_PIXEL_SIZE_IN_BYTES be sizeof(EPixelFormat::[…]) ?

Alright, so I think I’ve got it set up right.

I seem to be hitting an error:

Unhandled exception at 0x00007FF9986C9959 (nvwgf2umx.dll) in UE4Editor.exe: 0xC0000005: Access violation reading location 0x0000021666060800

I imagine this means some of the data im passing it isn’t working correctly?

edit:: it breaks on the line

ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(

  1. Make sure your texture is dynamic 2) The command scheduled by ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER will execute at a later time, from the rendering thread. Make sure you don’t delete pData before it gets used.

Thanks a ton! You just took my game from 15fps to 60fps.

You’re the man. Took a minute to figure out what the problem was with my array allocation but I got her done and award you a million digital brownies

Can anyone provide an example of how to do this for 32 bit float data? Im strugging to figure out how to get this code to display correctly

#include "DynamicTexture.h"

void SetDynamicTexturePixelsToColor(DynamicTexturePixel* pixels, int32 numPixels,
                                    DynamicTexturePixel color)
{
	if (pixels)
	{
		for (int i = 0; i < numPixels; i++)
			pixels[i] = color;
	}
}

DynamicTexture::~DynamicTexture()
{
	if (Pixels)
		delete[] Pixels;
}

void DynamicTexture::Initialize(USkeletalMeshComponent* mesh, int32 materialIndex, int32 width,
                                int32 height)
{
	if (!mesh)
		return;

	Width = width;
	Height = height;

	if (!Width || !Height)
	{
		return;
	}

	{
		Texture2D = UTexture2D::CreateTransient(Width, Height, PF_R32_FLOAT);
		Texture2D->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap;
		Texture2D->SRGB = 0;
		Texture2D->AddToRoot();
		Texture2D->UpdateResource();
	}

	{
		NumPixels = Width * Height;
		if (Pixels)
			delete[] Pixels;
		Pixels = new DynamicTexturePixel[NumPixels];

		SetDynamicTexturePixelsToColor(Pixels, NumPixels, {1.0, 0.0, 0.0, 1.0});
	}
	
	{
		UMaterialInstanceDynamic* material =
		    mesh->CreateAndSetMaterialInstanceDynamic(materialIndex);

		if (!material)
			return;

		DynamicMaterials.Empty();
		DynamicMaterials.Add(material);

		for (UMaterialInstanceDynamic* dynamicMaterial : DynamicMaterials)
		{
			if (dynamicMaterial)
				dynamicMaterial->SetTextureParameterValue("DynamicTextureParam", Texture2D);
		}
	}
}

void DynamicTexture::Update()
{
	uint8* data = reinterpret_cast<uint8*>(&Pixels);

	UTexture2D* inputText = Texture2D;

	ENQUEUE_RENDER_COMMAND(UpdateTextureRegionsData)
	([data, inputText](FRHICommandListImmediate& RHICmdList)
	{
		FUpdateTextureRegion2D region;
		region.SrcX = 0;
		region.SrcY = 0;
		region.DestX = 0;
		region.DestY = 0;
		region.Width = 138;
		region.Height = 139;
		FTexture2DResource* resource = (FTexture2DResource*)inputText->Resource;

		RHIUpdateTexture2D(
			resource->GetTexture2DRHI(),
			0,
			region,
			region.Width * sizeof(DynamicTexturePixel),
			data);
	});
}

#pragma once

#include "CoreMinimal.h"
#include "Engine/Texture2D.h"
#include "Rendering/Texture2DResource.h"
#include "GameFramework/GameModeBase.h"


struct DynamicTexturePixel
{
	float b;
	float g;
	float r;
	float a;
};

void SetDynamicTexturePixelsToColor(DynamicTexturePixel* pixels, int32 numPixels,
                                    DynamicTexturePixel color);

struct DynamicTexture
{
private:
	TArray<class UMaterialInstanceDynamic*> DynamicMaterials;
	UTexture2D* Texture2D;

public:
	int32 Width;
	int32 Height;
	int32 NumPixels;
	DynamicTexturePixel* Pixels;
	~DynamicTexture();

	void Initialize(USkeletalMeshComponent* mesh, int32 materialIndex, int32 width,
	                int32 height);

	void Update();
};