I have some code that works fine on PC but not iOS. I need to access the pixel data of a rendertarget to save as a jpg, this is mostly working barring locking the top mip’s pixel data.
When I use the render target as a source texture what I want to see gets rendered. When I force the pixel values of the generated UTexture2D to a pattern I see the pattern as expected but I don’t see valid data when I lock the top mip.
As mentioned this works on PC, but I need it to work on iOS too. If I have to I’ll do this all in ObjectiveC on a UIImage but that seems like an overkill and will be messy - plus make it harder to apply cool UE4 level materials.
In the code below RenderTexture is a UTextureRenderTarget2D. As far as I can tell it’s working perfectly.
static int iTest = -1;
UTexture2D *USharedRenderTarget::Create2DTexture() const
{
//RenderTexture is valid, I can render it's contents but I'd like to access the data directly to save
//as a jpg or png - right now I'm just trying to get it into a UTexture2D and render to verify.
UTexture2D *p2dTex = UTexture2D::CreateTransient(RenderTexture->SizeX, RenderTexture->SizeY, RenderTexture->GetFormat());
#if WITH_EDITORONLY_DATA
p2dTex->MipGenSettings = TMGS_NoMipmaps;
#endif
p2dTex->SRGB = RenderTexture->SRGB;
p2dTex->UpdateResource(); //Apply these settings
// Read the pixels from the RenderTarget and store them in a FColor array
TArray<FColor> SurfData;
FRenderTarget *RenderTarget = RenderTexture->GameThread_GetRenderTargetResource();
RenderTarget->ReadPixels(SurfData);
// Lock and copies the data between the textures
auto* TextureData = (FColor*)p2dTex->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
//A few different tests
if (iTest == 0)
{
//Ignoring TextureData
//Does work - creates texture pattern expected
for (int iY=0 ; iY<RenderTexture->SizeY ; iY++)
{
float fY = (float)iY / (float)(RenderTexture->SizeY-1);
uint8 g = (uint8)(fY * 255.0f);
uint8 a = 255;
for (int iX=0 ; iX<RenderTexture->SizeX ; iX++)
{
auto &rCol = TextureData[iX + iY*RenderTexture->SizeX];
float fX = (float)iX / (float)(RenderTexture->SizeX-1);
rCol.R = (uint8)(fX * 255.0f);
rCol.B = (uint8)((fX + fY) * 255.0f);
rCol.A = a;
rCol.G = g;
}
}
}
else if (iTest == 1)
{
//Does NOT work (actually it was just to remove the Memcpy as a test)
FColor *pSrc =SurfData.GetData();
FColor *pDest = TextureData;
int iRemain = SurfData.Num();
while (iRemain)
{
*pDest = *pSrc;
--iRemain;
++pSrc;
++pDest;
}
}
else if (iTest == 2)
{
//Does not compile - these Lock / Unlock Functions are not available outside the render code?
/*
uint32 SrcStride;
uint32 DestStride;
void* Src = RHILockTexture2D( RenderTexture, 0, RLM_ReadOnly, SrcStride, false );
void* Dst = RHILockTexture2D( p2dTex, 0, RLM_WriteOnly, DestStride, false );
check(SrcStride == DestStride);
FMemory::Memcpy( Dst, Src, RenderTexture->SizeX * RenderTexture->SizeY * SrcStride);
RHIUnlockTexture2D( RenderTexture, 0, false );
RHIUnlockTexture2D( p2dTex, 0, false );
*/
}
else
{
//Does NOT work, original code
const int32 TextureDataSize = SurfData.Num() * 4;
FMemory::Memcpy(TextureData, SurfData.GetData(), TextureDataSize);
}
p2dTex->PlatformData->Mips[0].BulkData.Unlock();
// Apply Texture changes to GPU memory
p2dTex->UpdateResource();
return p2dTex;
}
Setup code for reference:
USharedRenderTarget::USharedRenderTarget(const class FObjectInitializer& PCIP)
: Super(PCIP)
{
RenderTexture = PCIP.CreateDefaultSubobject<UTextureRenderTarget2D>(this, TEXT("RenderTexture"));
}
USharedRenderTarget::~USharedRenderTarget()
{
}
void USharedRenderTarget::Setup(int iWidth, int iHeight)
{
m_iWidth = iWidth;
m_iHeight = iHeight;
RenderTexture->ClearColor = FLinearColor::Black;
RenderTexture->InitAutoFormat(iWidth, iHeight);
RenderTexture->UpdateResourceImmediate();
}
bool USharedRenderTarget::Render(TFunction<void(FCanvas&)> renderFn)
{
FCanvas canvas(RenderTexture->GameThread_GetRenderTargetResource(), NULL, 0.0f, 0.0f, 0.0f, GMaxRHIFeatureLevel);
renderFn(canvas);
canvas.Flush_GameThread();
return true;
}