[BUG] Render Target->ReadPixels Android

FTextureRenderTargetResource->ReadPixels returns garbage data on Android(working in editor). Does anyone know another way to in the pixel information of a render target texture in order to save it as a thumbnail for a saved game?

Hey -

I’m unable to find a reference to ReadPixels in the FTextureRenderTargetResource class. Are you referring to FTextureRenderTargetCubeResource instead? If you’re able to reproduce the bad data in a fresh project, can you provide the steps used to help me test this issue on my end?

Created C++ actor with a blueprint callable function that takes in a USceneCaptureComponent2D, then get a reference to the FTextureTargetResource using USceneCaptureComponent2D->TextureTarget->GameThread_GetRenderTargetResource(). I then call readPixels from that which is in the resources base class FRenderTarget. Currently I am using hte imagewrapper to create and save a png file. Then packaged/deployed for Android(ASTC).

I tried following the steps you mentioned and used the following code in my test:

void AMyActor::PixelTest(USceneCaptureComponent2D* Something)
{
	FReadSurfaceDataFlags InFlags;
	FIntRect InRect;
	bool things = Something->TextureTarget->GameThread_GetRenderTargetResource()->ReadPixels(OutImageData, InFlags, InRect);
	if (GEngine)
	{
		if(things)
		GEngine->AddOnScreenDebugMessage(-1, 4.f, FColor::Magenta, TEXT("True"));
		else
		GEngine->AddOnScreenDebugMessage(-1, 4.f, FColor::Magenta, TEXT("False"));
	}
}

The OutImageData array was populated with four colors. When this function is called during runtime I get “True” printed to my screen as expected. Can you explain the garbage data you’re getting when you call ReadPixels()? If possible, could you provide a sample project that shows the issue?

Currently the outImageData is filled with RGB values of 0.link text

Hey -

It appers you are referring to how the FColor array is being set rather than the boolean that is returned from ReadPixels. After reviewing the project you sent I was able to reproduce the output you were seeing and have entered a report for the issue here Unreal Engine Issues and Bug Tracker (UE-38086) . You can track the report’s status as the issue is reviewed by our development staff.

Cheers

Thank you for submitting the bug report , I appreciate it.

Any workarounds for this?

On iOS it was fixed for me with the following code (called before GameThread_GetRenderTargetResource) but this doesn’t seem to help Android:

#if PLATFORM_IOS | PLATFORM_ANDROID
    ENQUEUE_UNIQUE_RENDER_COMMAND(WaitUntilIdle,
    {
        GRHICommandList.GetImmediateCommandList().BlockUntilGPUIdle();
    });
    FlushRenderingCommands();
#endif

Hey -

Unfortunately there is no known workaround at the moment for this issue.

I have no issues reading out a rendertarget on android by doing it the following way:
pre-warning: i did not test out the following psuedocode, but its basicly a bare minimal version of the code that i’m using.

struct FGetTexture
{
	FUpdateCaptureCubemap(int32 TextureWidthHeightInPixels,FRenderTarget* pSrcRenderTargetResource)
	{
		srcRenderTargetResource = pSrcRenderTargetResource);
		TextureSize= TextureWidthHeightInPixels;
		dstBuffer.AddUninitialized(TextureSize*TextureSize);
	}
	TArray<FColor> dstBuffer;
	FRenderTarget* srcRenderTargetResource;
	int32 TextureSize;
};

FGetTexture *pGetTexture = new FGetTexture(TextureWidthHeightInPixels,RenderTarget->GameThread_GetRenderTargetResource());
  
ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(GetTexture, FGetTexture *, pGetTexture, pGetTexture,
{
		FReadSurfaceDataFlags DefaultFlags(RCM_UNorm, CubeFace_MAX);
		RHICmdList.ReadSurfaceData(pGetTexture->srcRenderTargetResource->GetRenderTargetTexture(),FIntRect(0, 0, pGetTexture->TextureSize,pGetTexture->TextureSize),pGetTexture->dstBuffer,DefaultFlags);

		// Now do something with pGetTexture->DstBuffer either/and 
		// - upload it to a texture
		// - memcpy it to a different (make it Threadsafe) (allocated in your class) buffer
		// - start your next task here with the temp buffer as input, with the CreateTask function

		// do not forget to delete your temp buffer
		delete pGetTexture;
});

Thanks . I implemented a version of that but still all 0’s in the result.

I’m not sure if the problem is getting the surface or the previous rendering to that surface is failing though I have other render to surface points that are working (using the render target as a texture) and have reduced my test render to clearing the surface to one color and drawing a rectangle to another.

The RenderTarget setup is pretty straightforward:

    RenderTexture = PCIP.CreateDefaultSubobject<UTextureRenderTarget2D>(this, TEXT("RenderTexture"));
	RenderTexture->ClearColor = FLinearColor::Black;
	RenderTexture->SRGB = false;
	RenderTexture->bNeedsTwoCopies = false;
	RenderTexture->InitAutoFormat(iWidth, iHeight);
	RenderTexture->UpdateResourceImmediate();

If you can use the rendertarget correctly as a source texture, then i assume that the render itself does work. Maybe its your setup code for the RenderTarget, i have (minimized, not tested) the following:

In your initialize function

RenderTarget = NewObject<UTextureRenderTarget2D>(GetTransientPackage(), NAME_None, RF_Transient);
check(RenderTarget);
RenderTarget->ClearColor = FLinearColor(0.0, 0.0, 0.0);
RenderTarget->InitCustomFormat(TextureSizeWidthHeight, TextureSizeWidthHeight, PF_B8G8R8A8,true);
RenderTarget->UpdateResourceImmediate();

// then i use this RenderTarget in the SceneCaptureComponent
CaptureComp2D= NewObject<USceneCaptureComponent2D>(GetOwner());
check(CaptureComp2D);

CaptureComp2D->TextureTarget = RenderTarget;

// i call the update in the tick function myself
CaptureComp2D->bCaptureEveryFrame = false;

CaptureComp2D->MaxViewDistanceOverride = MaxViewDistance;
CaptureComp2D->SetAbsolute(false, false, true);

// This seemed to be important to get it working for mobile (in my case, i want linear colors), after they updated the Scenecapture component in 4.14.
// Also in 4.14 my target textures got flipped vertically, so i unflip them manually and reupload to my custom target texture in the ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER
CaptureComp2D->CaptureSource = SCS_SceneColorHDRNoAlpha;

CaptureComp2D->AttachToComponent(pOwner->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
CaptureComp2D->RegisterComponent();

// As no frame has been rendered yet.
FrameRendered = -1;

In the Tick function

// Here we retrieve the render that we did in the last tick
if (FrameRendered > -1)
{
	ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER .....
}
// Here we set the location/rotation of capturecomp2d and then kick of a Render
CaptureComp2D->CaptureScene();
if(FrameRendered < 0)
	FrameRendered = 0;
else
	FrameRendered++;

I get the same problem in UE 4.25: reading a render target’s pixels on Android gives all 0s, no matter the format or timing of reading. I would love to know if this is supposed to be solved, or should be fixed in a later version, or will never be fixed?