본문 바로가기

프로그래밍/Unreal

Unreal의 스레드와 단일 스레드로 실행시키기 (-norenderthread)

 언리얼은 두 개의 메인 스레드로 돌고 있는데, 하나는 우리가 아는 게임 스레드이고 나머지 하나는 한 틱 뒤에서 이 게임스레드를 뒤쫓고 있는 렌더 스레드이다.

 

 게임 스레드가 월드의 변경점을 렌더 스레드에 반영시키기 위해서는 ENQUEUE_RENDER_THREAD라는 매크로를 통해 람다 함수로 이를 렌더 큐에 쌓아두는 방식으로 접근한다.

 

void BeginInitResource(FRenderResource* Resource)
{
    ENQUEUE_RENDER_COMMAND(InitCommand)(
        [Resource](FRHICommandListImmediate& RHICmdList)
        {
            Resource->InitResource();
        });
}

 

 RenderResource.cpp에 정의되어있는 BeginInitResource가 바로 그렇다. 단순히 오브젝트가 들고 있던 리소스의 InitResource()를 호출해주는 동작이지만, 이를 렌더 스레드에서 실행시켜줄 수 있도록 ENQUEUE_RENDER_THREAD로 감싸주는 것이다.

 

 이 렌더 커맨드들은 큐에 쌓여있다가 렌더스레드에서 처리되기 때문에, 앞에서 말했던 대로 게임 스레드를 한 틱 늦게 쫓아가면서 순차적으로 실행되는 구조이다.

 

 그런데 이런 구조가 번거로워지는 경우가 종종 있다. 단적인 예로는 렌더 스레드에서 발생하는 버그를 디버그 하는 경우이다.

 

분명 이 리소스를 어떤 오브젝트에서 건드렸을 것인데, 렌더 큐에 쌓여있는 람다를 호출하는 방식이다보니 콜 스택으로는 확인이 안 된다거나 하는 경우다.

 

 그런 경우를 위해 언리얼에서는 이 렌더 스레드를 돌지 않도록 만드는 커맨드를 제공해주고 있다.

 실행 인자 -norenderthreadToggleRenderingThread 커맨드다. ENQUEUE_RENDER_COMMAND를 조금 더 살펴보면, 

 

template <typename TSTR, typename LAMBDA>
void EnqueueUniqueRenderCommand(LAMBDA& Lambda)
{
    if (IsInRenderingThread())
    {
        FRHICommandListImmediate& RHICmdList = GetImmediateCommandList_ForRenderCommand();
        Lambda(RHICmdList);
    }
    else
    {
        if (ShouldExecuteOnRenderThread)
            CreateTask().ConstructAndDisplayWhenReady(Forward<LAMBDA>(Lambda));
        else
            UniqueRenderCommandType TempCommand(Forward<LAMBDA>(Lambda));
            TempCommand.DoTask(ENamedThreads::GameThread);
    }
}

 

 대충 위와 같은 구조로 되어있는 것을 확인할 수 있다. 현재 쓰레드가 렌더링 스레드 안이라면, 들어온 람다를 즉시 실행시켜주고, 그렇지 않다면 이 커맨드가 렌더 스레드에서 실행되어야 할 필요가 있는지 체크한 후, GraphTask에 넣어 실행되기를 기다린다.

 

 위에서 말한 대로 실행 인자를 -norenderthread를 추가하여 실행시켜주는 경우, FGenericPlatformMisc::UseRenderThread() 함수에서 false를 반환하여 아예 렌더 스레드를 만들지 않는다.

 

렌더 스레드를 만들지 않으면서 ShouldExecuteOnRenderThread가 false가 되고 게임 스레드에서 렌더 커맨드가 즉시 실행되게 된다.

 

 ToggleRenderingThread 커맨드의 경우에는 실행 도중에도 이를 바꿀 수가 있는데, 현재 큐에 쌓여있는 렌더링 커맨드와 스레드 처리 등의 코드가 있는 것을 확인할 수 있다. (UnrealEngine.cpp의 HandleToggleRenderingThreadCommand 함수 참조)

 

 당연히 두 개의 쓰레드에서 처리하던 작업을 하나의 스레드로 합치기 때문에 성능은 저하되지만, 렌더링 커맨드를 디버그 하고 싶다면 굉장히 유용한 기능이다.

 

'프로그래밍 > Unreal' 카테고리의 다른 글

[UE4] Virtual Texturing과 Clipmap  (0) 2022.08.11
[Unreal] 클래스의 Prefix를 F로 사용하는 이유.  (0) 2022.05.01
UE4 -waitforattach  (0) 2022.03.30
GetTypeHash의 구현  (0) 2021.02.23
TEnableIf  (0) 2020.11.24