You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
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]
160 lines
4.7 KiB
C++
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;
|
|
});
|
|
}
|