PART 8 - 강의 1/6

UE5 스레드 아키텍처

Game Thread, Render Thread, RHI Thread의 역할과 동기화 이해

01

주요 스레드 개요

UE5의 핵심 스레드들

Game Thread

메인 게임 로직 실행

  • 입력 처리
  • 게임플레이 로직
  • AI / Behavior Tree
  • 물리 시뮬레이션
  • 애니메이션 업데이트
Render Thread

씬 렌더링 준비

  • 씬 가시성 계산
  • 드로우 명령 생성
  • 머티리얼 파라미터
  • 라이팅 계산
  • 셰도우 맵 준비
RHI Thread

GPU 명령 번역

  • 드로우 명령 번역
  • GPU API 호출
  • 리소스 바인딩
  • 커맨드 버퍼 제출
Worker Threads

병렬 작업 처리

  • Task Graph 작업
  • 에셋 비동기 로딩
  • 물리 병렬 계산
  • AI 경로 탐색
02

프레임 파이프라인

스레드 간 시간 관계

Frame N+1 | Frame N | Frame N-1
Game Thread
입력, 물리, AI, 게임 로직
Render Thread
가시성, 드로우 명령 준비
RHI Thread
GPU 명령 제출
핵심 포인트

Render Thread는 Game Thread보다 1-2 프레임 뒤처집니다. 이로 인해 Game Thread에서 변경한 데이터가 즉시 화면에 반영되지 않습니다. 데이터 동기화에 주의해야 합니다.

03

스레드 확인 방법

현재 스레드 판별하기

C++
// 현재 스레드 확인 if (IsInGameThread()) { // Game Thread에서 실행 중 UE_LOG(LogTemp, Log, TEXT("Running on Game Thread")); } if (IsInRenderingThread()) { // Render Thread에서 실행 중 } if (IsInRHIThread()) { // RHI Thread에서 실행 중 } // Game Thread 강제 체크 (디버그용) check(IsInGameThread()); // 어서션 - 조건 만족하지 않으면 크래시 checkf(IsInGameThread(), TEXT("This function must be called from Game Thread!"));
04

스레드 간 통신

다른 스레드에 작업 전달

Render Thread로 명령 전송

C++
// Game Thread에서 Render Thread로 명령 전송 ENQUEUE_RENDER_COMMAND(MyRenderCommand)( [CapturedData = this->SomeData](FRHICommandListImmediate& RHICmdList) { // 여기는 Render Thread에서 실행됨 // 주의: 캡처는 반드시 값으로! (참조 캡처 금지) ProcessOnRenderThread(CapturedData); } ); // 완료 대기 (동기화 필요 시) FlushRenderingCommands(); // Game Thread 블로킹!
Lambda 캡처 주의

Render Command의 람다에서 참조 캡처([&])는 금지입니다. Render Thread가 실행될 때 원본 데이터가 이미 소멸되었을 수 있습니다. 항상 값 캡처([=] 또는 명시적 값 캡처)를 사용하세요.

Game Thread로 복귀

C++
// 백그라운드 스레드에서 Game Thread로 작업 전달 AsyncTask(ENamedThreads::GameThread, [this]() { // Game Thread에서 실행됨 // UI 업데이트, UObject 접근 등 안전 OnWorkComplete(); });
05

스레드 안전성 규칙

멀티스레딩 기본 원칙

핵심 규칙
  • UObject는 Game Thread에서만 접근 - 생성, 수정, 삭제 모두 Game Thread에서
  • Render Thread 데이터 분리 - FRenderResource 등 별도 데이터 구조 사용
  • 공유 데이터는 동기화 - FCriticalSection, FRWLock 사용
  • 람다 캡처는 값으로 - 특히 다른 스레드로 전달 시
  • 블로킹 최소화 - FlushRenderingCommands는 꼭 필요할 때만
C++ - 잘못된 예 vs 올바른 예
// 잘못된 예 - Render Thread에서 UObject 접근 ENQUEUE_RENDER_COMMAND(BadCommand)( [this](FRHICommandListImmediate& RHICmdList) { // 위험! UObject 접근 MyActor->GetActorLocation(); // 크래시 가능! } ); // 올바른 예 - 데이터 복사 후 전달 FVector CachedLocation = MyActor->GetActorLocation(); ENQUEUE_RENDER_COMMAND(GoodCommand)( [CachedLocation](FRHICommandListImmediate& RHICmdList) { // 안전! 복사된 값 사용 ProcessLocation(CachedLocation); } );
SUMMARY

핵심 요약

핵심 포인트
  • Game Thread - 게임 로직, 입력, 물리, AI 처리
  • Render Thread - 씬 가시성 계산, 드로우 명령 생성 (1-2 프레임 지연)
  • RHI Thread - GPU API 호출, 커맨드 버퍼 제출
  • ENQUEUE_RENDER_COMMAND - Render Thread로 명령 전송
  • AsyncTask(GameThread) - Game Thread로 복귀
  • UObject는 Game Thread 전용 - 다른 스레드에서 접근 금지
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: 스레드 구조 파악

stat Threading 명령으로 Game Thread, Render Thread, RHI Thread의 시간을 확인하세요. UE5의 스레드 모델(Game Thread, Render Thread, Worker Threads)을 도식화하세요.

실습 2: FRunnable로 커스텀 스레드 생성

FRunnable을 상속하여 백그라운드에서 RPG 월드 데이터를 처리하는 워커 스레드를 생성하세요. Init(), Run(), Stop()을 구현하고, FRunnableThread::Create()로 시작하세요.

심화 과제: 스레드 안전한 데이터 공유 구현

Game Thread와 Worker Thread 간에 FCriticalSection(FScopeLock)으로 보호되는 공유 큐를 구현하세요. 패스파인딩 결과나 절차적 생성 데이터를 스레드 간에 안전하게 전달하세요.