
원래 RHI는 일부 리소스 관리 및 명령 인터페이스를 포함하여 D3D11 API를 기반으로 설계되었습니다.
언리얼 엔진은 모바일, 콘솔, PC 등 다양한 플랫폼을 지원하는 유비쿼터스 툴이기 때문에 플랫폼에 따라 다양한 API(DirectX, Vulkan, OpenGL, Metal)를 사용할 수 있습니다. 이를 해결하기 위해 언리얼은 게임 엔진 렌더링 코드를 최대한 포괄적으로 유지할 수 있도록 이러한 모든 API에 대한 인터페이스를 추상화했습니다.
이는 아래와 같이 다양한 렌더 스레드를 사용하여 달성됩니다:

사진에는 게임 스레드, 렌더 스레드, RHI 스레드가 있습니다.
언리얼엔진에서 이해해야 할 중요한 점은 (다른 특정 경우를 제외한다면) 렌더링되는 오브젝트가 게임 스레드와 렌더 스레드 사이에 표현된다는 것입니다.
언리얼 문서를 살펴보면 이에 대한 설명과 함께 Primitive Component(게임 스레드), Primitive Proxy(렌더 스레드)라는 명명 규칙을 확인할 수 있습니다.
Note. RHI Immediate는 실제로 즉각적임을 의미하며 일반적으로 지연 명령인 regular RHI command와 동일한 호출이 아닙니다.

FRenderResource는 렌더링 스레드의 렌더링 리소스 표현입니다. 이 리소스는 게임 스레드와 렌더 스레드 사이의 중간 데이터로 렌더링 스레드에서 관리하고 전달합니다.
/*
* 렌더링 스레드가 소유하는 렌더링 리소스입니다.
* 참고 - 이 클래스에 새로운 가상 메서드를 추가하려면 FViewport/FDummyViewport에 스텁을 추가해야 하며,
* 그렇지 않으면 특정 모듈에 링크 오류가 발생할 수 있습니다.
*/
class RENDERCORE_API FRenderResource
{
public:
///////////////////////////////////////////////////////////////////////////////////
// 렌더링 리소스를 비동기적으로 초기화/해제하는 동안에는 다음 메서드를 호출할 수 없습니다.
/** 현재 초기화된 모든 렌더링 리소스를 해제합니다. */
static void ReleaseRHIForAllResources();
/** RHI가 초기화되기 전에 초기화된 모든 리소스를 초기화합니다. */
static void InitPreRHIResources();
/**
* 이 리소스에서 사용하는 동적 RHI 리소스 및/또는 RHI 렌더 타깃을 초기화합니다.
* 리소스가 초기화될 때 또는 모든 RHI 리소스를 리셋할 때 호출됩니다.
* D3D 디바이스 리셋 후 초기화해야 하는 리소스는 이 함수를 구현해야 합니다.
* 렌더링 스레드에서만 호출됩니다.
*/
virtual void InitDynamicRHI() {}
/**
* 이 리소스가 사용하는 동적 RHI 리소스 및/또는 RHI 렌더 타깃 리소스를 해제합니다.
* 리소스가 해제될 때 또는 모든 RHI 리소스를 리셋할 때 호출됩니다.
* D3D 디바이스 리셋 전에 해제해야 하는 리소스는 이 함수를 구현해야 합니다.
* 렌더링 스레드에서만 호출됩니다.
*/
virtual void ReleaseDynamicRHI() {}
/**
* 이 리소스가 사용하는 RHI 리소스를 초기화합니다.
* 리소스와 RHI가 모두 초기화된 상태로 진입할 때 호출됩니다.
* 렌더링 스레드에서만 호출됩니다.
*/
virtual void InitRHI() {}
/**
* 이 리소스가 사용 중인 RHI 리소스를 해제합니다.
* 리소스와 RHI가 모두 초기화된 상태에서 벗어날 때 호출됩니다.
* 렌더링 스레드에서만 호출됩니다.
*/
virtual void ReleaseRHI() {}
/**
* 리소스를 초기화합니다.
* 렌더링 스레드에서만 호출됩니다.
*/
virtual void InitResource();
/**
* 리소스를 삭제할 준비를 합니다.
* 렌더링 스레드에서만 호출됩니다.
*/
virtual void ReleaseResource();
/**
* 리소스의 RHI 리소스가 초기화되었다면 해제했다가 다시 초기화합니다.
* 그렇지 않으면 아무것도 하지 않습니다.
* 이 함수는 렌더링 스레드에서만 호출됩니다.
*/
void UpdateRHI();
(...)
};
FRenderResource 클래스를 상속하는 많은 서브클래스가 있어 렌더링 스레드가 게임 스레드의 데이터와 연산을 다양한 추상화 수준을 가진 RHI 스레드로 전송할 수 있습니다.
FRHIResource는 참조 카운팅(reference counting), 지연 삭제(delayed deletion), 추적 및 런타임 데이터 마킹(tracking & runtime data marking)에 이용됩니다.
FRHIResource는 state blocks, shader bindings, regular shaders, pipeline states, buffers, textures, views 및 RHI가 사용하는 기타 유형으로 나눌 수 있습니다. 이 클래스로 플랫폼별 유형을 만들 수 있다는 점에 유의해야 합니다. 자세한 내용은 FRHIUniformBuffer 클래스의 소스를 확인하세요.
/** RHI 리소스의 기본 유형입니다. */
class RHI_API FRHIResource
{
public:
UE_DEPRECATED(5.0, "FRHIResource(bool) is deprecated, please use FRHIResource(ERHIResourceType)")
FRHIResource(bool InbDoNotDeferDelete=false)
: ResourceType(RRT_None)
, bCommitted(true)
#if RHI_ENABLE_RESOURCE_INFO
, bBeingTracked(false)
#endif
{
}
FRHIResource(ERHIResourceType InResourceType)
: ResourceType(InResourceType)
, bCommitted(true)
#if RHI_ENABLE_RESOURCE_INFO
, bBeingTracked(false)
#endif
{
#if RHI_ENABLE_RESOURCE_INFO
BeginTrackingResource(this);
#endif
}
virtual ~FRHIResource()
{
check(IsEngineExitRequested() || CurrentlyDeleting == this);
check(AtomicFlags.GetNumRefs(std::memory_order_relaxed) == 0); // this should not have any outstanding refs
CurrentlyDeleting = nullptr;
#if RHI_ENABLE_RESOURCE_INFO
EndTrackingResource(this);
#endif
}
FORCEINLINE_DEBUGGABLE uint32 AddRef() const
{...};
private:
// 모든 곳에 강제로 인라인 처리하는 것을 방지하기 위해 별도의 함수를 사용합니다.
// 코드 크기와 성능 모두에 도움이 됩니다.
inline void Destroy() const
{...};
public:
FORCEINLINE_DEBUGGABLE uint32 Release() const
{...};
FORCEINLINE_DEBUGGABLE uint32 GetRefCount() const
{...};
static int32 FlushPendingDeletes(FRHICommandListImmediate& RHICmdList);
static bool Bypass();
bool IsValid() const
{...};
void Delete()
{...};
inline ERHIResourceType GetType() const { return ResourceType; }
#if RHI_ENABLE_RESOURCE_INFO
// 사용 가능한 경우 리소스 정보를 가져옵니다.
// 리소스 정보가 데이터로 채워진 경우 true를 반환해야 합니다.
virtual bool GetResourceInfo(FRHIResourceInfo& OutResourceInfo) const
{...};
static void BeginTrackingResource(FRHIResource* InResource);
static void EndTrackingResource(FRHIResource* InResource);
static void StartTrackingAllResources();
static void StopTrackingAllResources();
#endif
private:
class FAtomicFlags
{
static constexpr uint32 MarkedForDeleteBit = 1 << 30;
static constexpr uint32 DeletingBit = 1 << 31;
static constexpr uint32 NumRefsMask = ~(MarkedForDeleteBit | DeletingBit);
std::atomic_uint Packed = { 0 };
public:
int32 AddRef(std::memory_order MemoryOrder)
{...};
int32 Release(std::memory_order MemoryOrder)
{...};
bool MarkForDelete(std::memory_order MemoryOrder)
{...};
bool UnmarkForDelete(std::memory_order MemoryOrder)
{...};
bool Deleteing()
{...};
mutable FAtomicFlags AtomicFlags;
const ERHIResourceType ResourceType;
uint8 bCommitted : 1;
#if RHI_ENABLE_RESOURCE_INFO
uint8 bBeingTracked : 1;
#endif
static std::atomic<TClosableMpscQueue<FRHIResource*>*> PendingDeletes;
static FHazardPointerCollection PendingDeletesHPC;
static FRHIResource* CurrentlyDeleting;
// 일부 API는 내부 참조 카운팅을 수행하지 않으므로 리소스를 삭제하기 전에 몇 프레임을 더 기다려야 합니다.
// GPU가 완전히 완료했는지 확인하기 위해 몇 프레임을 더 기다려야 합니다.
// 이렇게 하면 값비싼 펜스 등을 피할 수 있습니다.
struct ResourcesToDelete
{...};
};
RHI Command list는 명령 개체 그룹을 관리하고 실행하는 데 사용되는 명령 대기열입니다. FRHICommandList의 부모 클래스는 FRHICommandListBase입니다.
FRHICommandListBase는 명령 대기열에 필요한 기본 데이터(Command list, Device context)와 인터페이스(Command refresh, wait, enqueue, memory allocation)를 정의합니다.
FRHIComputeCommandList는 컴퓨팅 셰이더 간의 인터페이스, GPU 리소스의 상태 전환 및 셰이더 파라미터 설정을 정의합니다.
FRHICommandList는 버텍스 셰이더, 픽셀 셰이더, 지오메트리 셰이더, 프리미티브 드로잉, 셰이더 파라미터 및 리소스 관리 등 일반적인 렌더링 파이프라인의 인터페이스를 정의합니다.
마지막으로 그래픽 API 관련 연산 집합을 정의하는 또 다른 인터페이스 클래스인 RHIContext와 DynamicRHI도 있습니다. 앞서 언급했듯이 일부 API는 명령을 병렬로 처리할 수 있으므로 이 별도의 객체는 이를 정의하는 데 사용됩니다.
요약하자면, RHI 클래스는 언리얼에서 다양한 그래픽 API와 통신하는 데 사용되는 가장 낮은 레벨의 추상화입니다. 나열된 것들은 여러분이 알아야 할 주요 클래스입니다. RHI에 대한 자세한 내용은 레퍼런스 섹션에서 자세히 살펴볼 수 있습니다. 또한 명령 목록이 이해가 되지 않는다면 API를 사용하여 기본 명령 목록/버퍼가 GPU에 어떻게 처리되는지 찾아보세요.