How to prevent pausing caused by scene capture?

Hey community,

I am working on a simulation that has multiple cameras that all need to take scene captures every time a HTTP request is being made, and then send the captured scene back in HTTP response. Those requests come multiple times in a minute, so quite frequently. The issue is the small pause that the scene capture causes which will make the simulation super laggy. I tried handling it in another thread but it doesn’t work either since rendering stuff should not be done in background threads. This is one version I have tried:

***

requestGET() to handle the HTTP request:

bool ASimulationHttpServer::requestGET(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete)
{
	// reset waitForFence boolean
	myWaitForFence = true;

	if (!IsValid(mySimulationGameState) || !myServerStarted || myServerPort <= 0)
	{
		return false;
	}
	TArray<ACameraPawn*> arrayOfCameras = mySimulationGameState->getCamerasInGroup(myCameraGroup->getMyCameraGroupID());
	for (auto& camera : arrayOfCameras)
	{
		for (auto& queryParam : Request.QueryParams)
		{
			if (queryParam.Key == "id" && FCString::Atoi(*queryParam.Value) == camera->getMyCameraID())
			{
				camera->createAndCaptureScene();

				while(myWaitForFence)
				{
					if (camera->getIsFenceReady())
					{
						myWaitForFence = false;
						const std::vector<unsigned char> resultVector = camera->encodeToJpeg();
						if (!resultVector.empty())
						{
							TUniquePtr<FHttpServerResponse> response = FHttpServerResponse::Create(TEXT(""), TEXT("image/jpeg"));
							if (response != nullptr)
							{
								// Content-Type header
								TArray<FString> contentTypeValues;
								contentTypeValues.Add(TEXT("image/jpeg"));
								response->Headers.Add(TEXT("Content-Type"), contentTypeValues);

								// Add image data to response body
								response->Body.Append(reinterpret_cast<const uint8*>(resultVector.data()), resultVector.size());

								// Set the Content-Length header
								FString contentLengthValue = FString::Printf(TEXT("%d"), response->Body.Num());
								TArray<FString> contentLengthValues;
								contentLengthValues.Add(contentLengthValue);
								response->Headers.Add(TEXT("Content-Length"), contentLengthValues);

								OnComplete(MoveTemp(response));
								return true;
							}
							else
							{
								return false;
							}
						}
					}
				}
				return true;
			}
		}
	}
	return false;
}

Inside it the createAndCaptureScene():

void ACameraPawn::createAndCaptureScene()
{
    // reset readPixelsStarted and imagePixels in the beginning of scene capture
    myReadPixelsStarted = false;
    myImagePixels.Empty();

    if (IsValid(myRenderTarget))
    {
        myRenderTarget->SRGB = true;
        myRenderTarget->TargetGamma = 2.0f;

        mySceneCaptureComponent = FindComponentByClass<USceneCaptureComponent2D>();
        if (IsValid(mySceneCaptureComponent))
        {
            mySceneCaptureComponent->TextureTarget = myRenderTarget;

            // Capture Scene:
            mySceneCaptureComponent->CaptureScene();
            FTextureRenderTargetResource* renderTargetResource = myRenderTarget->GameThread_GetRenderTargetResource();
            readPixels(myImagePixels, renderTargetResource); // reads pixels and saves them in ImagePixels array (diy function)
        }
    }
}

I made a modified version of the ReadPixels() because in the engine version FlushRenderingCommands() is called which stops the thread for a while. So my version looks like:

bool ACameraPawn::readPixels(TArray< FColor >& OutImageData, FTextureRenderTargetResource* renderTargetResourceInstance, FReadSurfaceDataFlags InFlags, FIntRect InRect)
{
    // pretty much a straight copy from UnrealClient::ReadPixels() function to execute the pixel reading without the thread stopping FlushRenderingCommands() function;
    myReadPixelsStarted = true;

    if (InRect == FIntRect(0, 0, 0, 0))
    {
        InRect = FIntRect(0, 0, renderTargetResourceInstance->GetSizeXY().X, renderTargetResourceInstance->GetSizeXY().Y);
    }

    // Read the render target surface data back.	
    struct FReadSurfaceContext
    {
        FRenderTarget* SrcRenderTarget;
        TArray<FColor>* OutData;
        FIntRect Rect;
        FReadSurfaceDataFlags Flags;
    };

    OutImageData.Reset();
    FReadSurfaceContext Context =
    {
        renderTargetResourceInstance,
        &OutImageData,
        InRect,
        FReadSurfaceDataFlags(RCM_UNorm, CubeFace_MAX)
    };

    ENQUEUE_RENDER_COMMAND(ReadSurfaceCommand)(
        [Context](FRHICommandListImmediate& RHICmdList)
        {
            RHICmdList.ReadSurfaceData(
                Context.SrcRenderTarget->GetRenderTargetTexture(),
                Context.Rect,
                *Context.OutData,
                Context.Flags
            );
        });
    myReadPixelFence.BeginFence();

    return OutImageData.Num() > 0;
}

I have tried also AsyncTask:

void ACameraPawn::createAndCaptureScene(const std::function<void(const std::vector<unsigned char>&)>& resultCallback)
{
    if (IsValid(myRenderTarget))
    {
        myRenderTarget->SRGB = true;
        myRenderTarget->TargetGamma = 2.0f;

        mySceneCaptureComponent = FindComponentByClass<USceneCaptureComponent2D>();
        if (IsValid(mySceneCaptureComponent))
        {
            mySceneCaptureComponent->TextureTarget = myRenderTarget;

            // Capture Scene:
            mySceneCaptureComponent->CaptureScene();
            std::lock_guard<std::mutex> Lock(RenderTargetMutex);
            FRenderTarget* renderTargetResource = myRenderTarget->GameThread_GetRenderTargetResource();
            RenderTargetMutex.unlock();

            // add FRenderTarget and callback function in struct that will be passed in async task
            captureTaskParams taskParams;
            taskParams.renderTargetResource = renderTargetResource;
            taskParams.captureResultCallback = resultCallback;

            // create a new async task
            const auto asyncTraceTask = new FAutoDeleteAsyncTask<UAsyncTaskForSceneCapture>(this, taskParams);
            asyncTraceTask->StartBackgroundTask();
        }
    }
}

I also stumbled upon experimental Capture Scene Async class, but I guess that doesn’t work unless I move lot of the functionality to blueprints…? At least trying to do it in C++ failed.

The issue with all these is that it STILL always pauses the game thread. Is there any way to do this without the pausing happening?
Any help is appreciated! If more code or other explanation is needed, please let me know :slight_smile:

I am facing this same issue with scene capture stopping the game for a brief moment. Some hints on how to solve this would be greatly appreciated!

Or is it even possible to do asynchronous renders in UE? I believe it should somehow be possible since for example render target can render another image of the same scene without any interruptions: https://www.youtube.com/watch?v=c4YCMK9L9qI Or am I missing something here, is this render target somehow fundamentally different compared to how scene capture works?