How to access all faces of TextureRenderTargetCube?

Hello,

we’re using a TextureRenderTargetCube to map colors to raytraced points for our LiDAR sensor. Using UE4.27 we were able to extract each face of the TextureRenderTargetCube individually like this:

if (TextureTarget != NULL)
	{
		CubeResource = (FTextureRenderTargetCubeResource*)TextureTarget->GameThread_GetRenderTargetResource();

		if (CubeResource->IsInitialized() && CubeResource != NULL)
		{
			CubeResource->ReadPixels(ColorBufferPosX, FReadSurfaceDataFlags(RCM_UNorm, CubeFace_PosX));

...

TextureTarget is a member of the USceneCaptureComponentCube class of which our sensor is derived and has the type TextureRenderTargetCube.

Now using UE5.1 this doesn’t work anymore. Casting the TextureTarget to a FTextureRenderTargetCubeResource will trigger an assertion (in D3D12RenderTarget.cpp, line 656) that a Texture2D is expected. Therefore, I checked the implementation of the ReadRenderTargetHelper in KismetRenderingLibrary.cpp from the engine and stopped casting to a cube resource and used a regular TextureRenderResource instead:

FTextureRenderTarget2DResource* RTResource = (FTextureRenderTarget2DResource*)TextureTarget->GameThread_GetRenderTargetResource();
		FIntRect SampleRect(0, 0, RTResource->GetSizeX(), RTResource->GetSizeY());

		if (RTResource->IsInitialized() && RTResource != NULL)
		{
			FRenderTarget* RenderTarget = TextureTarget->GameThread_GetRenderTargetResource();
			RenderTarget ->ReadPixels(AColorBufferPosX, FReadSurfaceDataFlags(RCM_UNorm, CubeFace_PosX), SampleRect));

Since I’m passing the value CubeFace_PosX as an argument, I expect to recieve the data from a specific cube face. However, if I change the value to CubeFace_PosY or other sides, the result is identical.

How do I access the other sides of the cube?

Thanks in advance for your help.

I dug a little into the D3D12RenderTarget.cpp file and found out, that the cube faces may not be accessible at all. We have this if statement, where the cube face would be accessed if the Texture->getDesc().IsTextureCube():

D3D12RenderTarget.cpp, line 702:

	if (Texture->GetDesc().IsTextureCube())
	{
		uint32 D3DFace = GetD3D12CubeFace(InFlags.GetCubeFace());
		Subresource = CalcSubresource(InFlags.GetMip(), D3DFace, TextureRHI->GetNumMips());
	}

The function IsTextureCube checks if the dimension of the Texture is of a cube type:

RHIResources.h, line 1425-1428:

	bool IsTextureCube() const
	{
		return Dimension == ETextureDimension::TextureCube || Dimension == ETextureDimension::TextureCubeArray;
	}

Now the problem is that this will always return false, since the assertion on line 656 will make sure that the dimension of the TextureRHI always equals Texture2D.

D3D12RenderTarget.cpp, line 656:

check(TextureRHI->GetTexture2D());

RHIResources.h, line 1882:

inline FRHITexture2D* GetTexture2D() { return TextureDesc.Dimension == ETextureDimension::Texture2D ? this : nullptr; }

I have no clue if it matters that the assertion (line 656) is checking a FRHITexture and the if statement (line 702) testing a FD3D12Texture, but to me it looks like this could be a bug in the engine. What do you think?

I can’t remember my exact train of thought here, but I found a “solution” … or rather a workaround where this bug doesn’t exist:

When using the PF_FloatRGBA format for the TextureRenderTargetCube …

	UTextureRenderTargetCube* RenderTarget = NewObject<UTextureRenderTargetCube>();
	RenderTarget->Init(2048, PF_FloatRGBA);
	RenderTarget->UpdateResourceImmediate();
	TextureTarget = RenderTarget;

… it’s ok to cast to a FTextureRenderTargetCubeResource:

if (TextureTarget != NULL)
	{
		FTextureRenderTargetCubeResource* RTResource = (FTextureRenderTargetCubeResource*)TextureTarget->GameThread_GetRenderTargetResource();
		
		if (RTResource->IsInitialized() && RTResource != NULL)
		{
			if (!RTResource->ReadPixels(ImageDataPosX, FReadSurfaceDataFlags(RCM_UNorm, CubeFace_PosX))) UE_LOG(LogTemp, Warning, TEXT("LiDAR: PosX failed"));

This works because in D3D12RenderTarget.cpp then the method RHIReadSurfaceFloatData is used instead of GetStagingTexture. And since they both do the same more or less (to my very limited understanding), but RHIReadSurfaceFloatData doesn’t have the stupid/broken assertion it now works. Just need to convert the results to FColor and from BGR to RGB, but otherwise now it does what it should and I’m able to access all sides of the cube … yay just a week spent for a bug in the engine.

Let me know if there’s an alternative solution or if this assertion is intentional and how to avoid it …

cheers.

This is awesome! Do you happen to know if this same solution works for UE 5.3?