Files
UnrealEngineUWP/Engine/Source/Runtime/Landscape/Private/LandscapeAsyncTextureReadback.cpp
chris tchou 9c5b82db24 Fix for landscape taking a long time when creating a project from a template while using DirectX 11.
The GPU Readback will never return IsReady() within a loop in a single frame, so we must force it to complete anyways.

I chose to just have it force a completion after 5 ticks, which is just a simple metric that should keep it from being hit in the amortized case (readbacks come back in 3 ticks at most), but will quickly get hit in the BuildNow() case.

#rb jonathan.bard
#jira UE-207360

[CL 31744046 by chris tchou in ue5-main branch]
2024-02-22 19:12:44 -05:00

160 lines
4.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LandscapeAsyncTextureReadback.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "RenderGraphBuilder.h"
#include "RenderGraphUtils.h"
void FLandscapeAsyncTextureReadback::StartReadback_RenderThread(FRDGBuilder& GraphBuilder, FRDGTextureRef RDGTexture)
{
check(!bStartedOnRenderThread && !AsyncReadback);
check(RDGTexture->Desc.Format == PF_B8G8R8A8);
AsyncReadback = MakeUnique<FRHIGPUTextureReadback>(TEXT("LandscapeGrassReadback"));
AddEnqueueCopyPass(GraphBuilder, AsyncReadback.Get(), RDGTexture);
FIntVector Size = RDGTexture->Desc.GetSize();
TextureWidth = Size.X;
TextureHeight = Size.Y;
check(Size.Z == 1);
bStartedOnRenderThread = true;
}
void FLandscapeAsyncTextureReadback::FinishReadback_RenderThread()
{
check(bStartedOnRenderThread && AsyncReadback.IsValid());
int32 RowPitchInPixels = 0;
int32 BufferHeight = 0;
void* SrcData = AsyncReadback->Lock(RowPitchInPixels, &BufferHeight); // this will block if the readback is not yet ready
check(SrcData);
check(RowPitchInPixels >= TextureWidth);
check(BufferHeight >= TextureHeight);
if (!bCancel) // skip the copy work if we're cancelling
{
// copy into ReadbackResults
ReadbackResults.SetNumUninitialized(TextureWidth * TextureHeight);
// OpenGL does not really support BGRA images and uses channnel swizzling to emulate them
// so when we read them back we get internal RGBA representation
const bool bSwapRBChannels = IsOpenGLPlatform(GMaxRHIShaderPlatform);
if (!bSwapRBChannels && TextureWidth == RowPitchInPixels)
{
memcpy(ReadbackResults.GetData(), SrcData, TextureWidth * TextureHeight * sizeof(FColor));
}
else
{
// copy row by row
FColor* Dst = ReadbackResults.GetData();
FColor* Src = (FColor*)SrcData;
if (bSwapRBChannels)
{
for (int y = 0; y < TextureHeight; y++)
{
for (int x = 0; x < TextureWidth; x++)
{
// swap B and R channels when copying
Dst->B = Src->R;
Dst->G = Src->G;
Dst->R = Src->B;
Dst->A = Src->A;
Dst++;
Src++;
}
Src += RowPitchInPixels - TextureWidth;
}
}
else
{
for (int y = 0; y < TextureHeight; y++)
{
memcpy(Dst, Src, TextureWidth * sizeof(FColor));
Dst += TextureWidth;
Src += RowPitchInPixels;
}
}
}
}
AsyncReadback->Unlock();
AsyncReadback.Reset();
FPlatformMisc::MemoryBarrier();
bFinishedOnRenderThread = true;
}
bool FLandscapeAsyncTextureReadback::CheckAndUpdate(bool& bOutFinishCommandQueued, const bool bInForceFinish)
{
// if we already queued the finish commands to render thread, then we're just waiting on it signaling readback complete
if (bFinishQueuedFromGameThread)
{
return bFinishedOnRenderThread;
}
// if we haven't started, or if the readback is not yet ready, then we have nothing to do but wait
if (!bStartedOnRenderThread || (!bInForceFinish && !AsyncReadback->IsReady()))
{
return false;
}
// the readback was started and it is ready, but we have not yet queued the finish command.
// queue it to make the data available to the game thread
FLandscapeAsyncTextureReadback* Readback = this;
ENQUEUE_RENDER_COMMAND(FLandscapeAsyncTextureReadback_FinishReadback)(
[Readback, bInForceFinish](FRHICommandListImmediate& RHICmdList)
{
// sanity check the state
check(Readback->bStartedOnRenderThread && !Readback->bFinishedOnRenderThread);
check(Readback->AsyncReadback.IsValid() && (bInForceFinish || Readback->AsyncReadback->IsReady()));
Readback->FinishReadback_RenderThread();
});
bFinishQueuedFromGameThread = true;
bOutFinishCommandQueued = true;
return false;
}
void FLandscapeAsyncTextureReadback::CancelAndSelfDestruct()
{
// set the cancel flag, which will reduce work done by the finish command
bCancel = true;
// run a render thread finish command if it hasn't been queued yet
bool bNeedsFinish = !bFinishQueuedFromGameThread;
bFinishQueuedFromGameThread = true;
FLandscapeAsyncTextureReadback* Readback = this;
ENQUEUE_RENDER_COMMAND(FCancelAndSelfDestructCommand)(
[Readback, bNeedsFinish](FRHICommandListImmediate& RHICmdList)
{
check(Readback->bStartedOnRenderThread);
check(Readback->bCancel);
// if not yet finished, run the finish command
if (bNeedsFinish)
{
Readback->FinishReadback_RenderThread();
}
check(Readback->bFinishedOnRenderThread);
// self destruct
delete Readback;
});
}
void FLandscapeAsyncTextureReadback::QueueDeletionFromGameThread()
{
check(IsInGameThread());
check(bFinishedOnRenderThread);
FLandscapeAsyncTextureReadback* Readback = this;
ENQUEUE_RENDER_COMMAND(FLandscapeAsyncTextureReadback_CheckAndUpdate)(
[Readback](FRHICommandListImmediate& RHICmdList)
{
delete Readback;
});
}