2025. 2. 6. 11:47ㆍPot of Thoughts/Fragments of Think
현재 엔진에서 구현 중인 렌더러(또는 렌더링 시스템 전반)는 GPU 주도적 설계를 따르고 있다.
그렇다 보니 렌더링 가능한 물체에 대한 모든 정보를 GPU에 업로드 해두고, 바뀌는 정보를 감지하여 GPU 메모리에 데이터를 반영 해주도록(Replication) 구현 되어 있다.
문제는, 이들을 프로파일링 하였을 때 CPU 타임이 만만치 않았다는 것 이다. 물론 이 사실을 구현 초기에 프로파일링을 통해 인지 할 수 있었고 극도로 병렬화 될 수 있는 구조로 설계 및 서로 연관이 없는 데이터의 경우 서로 동시적(또는 병렬적)으로 처리 될 수 있도록 구현 하였다. 덕분에 CPU 타임은 생각보다 크게 줄일 수 있었다.
이중 특히 Transform의 경우 모든 렌더링 가능한 물체가 포함 하고 있는 만큼, 가장 큰 처리 시간을 필요로 한다. 예를 들어 10만개의 인스턴스를 처리하는데 있어서 약 2ms (약 200만 ns)의 시간이 소요 된다. 평균적으로 1개의 인스턴스를 처리하는데 20 ns(나노초)가 소요된다 볼 수 있다. 물론 지금도 현재 케이스로는 충분한 성능이라고 생각하지만 추후에 조금 더 최적화 할 수 있는 아이디어를 남겨 두고자 한다.
앞에서 본 10만개의 인스턴스에서 2ms는 물론 모든 물체가 Dynamic 한 경우이다(모든 인스턴스가 실시간으로 Trasnform의 데이터를 업데이트 하는 경우). 하지만 실제 게임에서는 동적인 물체(캐릭터, 차량, 적, NPC)보다 정적인 물체(건물, 산, 나무, 빌보드)인 경우가 대부분이다. 즉, 실제로 Replication의 대상이 될 수 있는 물체는 훨 씬 더 적다는 것 이다.
한 개의 인스턴스의 처리에서 20ns가 고정 비용이라고 생각하자. 그리고 위와 동일하게 10만개의 렌더링 가능한 물체가 있고 이중 2만개의 물체만이 동적이라고 생각해보자. 그렇다면 매 프레임 데이터가 변했는지 체크해야 할 대상에서 8만개가 제외 될 수 있다. 이렇게 되면 단순히 생각했을 때 0.4ms의 처리 시간만 필요로 하게 된다. 여기서 더 나아가서 Transform 외에 동시적으로 처리되고 있는 데이터들의 workload balancing이 더 원활하게 이루어 진다면, 전체적인 성능도 크게 나아 질 가능성이 높다.
엔진은 entt를 기반으로 한 Entity Component System 설계를 따르고 있기 때문에, 이는 단순히 Tag Component(데이터가 없는 컴포넌트)를 추가 하고, 업데이트 과정에서 해당 태그가 달려있는 객체를 필터링에서 제외 시켜주기만 하는 방식으로 쉽게 구현이 가능하다. StaticTransformTag는 생성 직후 초기값에서 더 이상 Transform 정보가 변하지 않는 렌더링 가능한 객체, 반대의 경우가 동적인 객체 이다. 또한 StaticTransformTag는 언제든지 제거되어 새롭게 Transform 정보를 업데이트 할 수 있고, 그 반대도 동일하다(일종의 Transform Replication Flag로서 작동).
실제로 현재도 이미, RenderableTag와 TransformComponent를 동시에 소유하는 객체들에 한해서 Transform 데이터를 GPU에 Replication 해주고 있다(Camera, Light와 같은 객체들은 실제로 Transform Matrix를 필요로 하진 않기에..).
여기서 예상되는 유일한 Drawback이라곤 게임 로직을 작성 할 때, StaticTransformTag를 고려 해야 한다는 점. Tag Component 자체가 추가적으로 메모리를 필요로 하진 않지만, 이를 엔티티가 소유하고 있다는 정보를 저장하기 위한 메모리 공간 오버헤드, 마지막으로 두가지 경우에 대한 처리를 따로 해야 하기 때문에 만약 장면의 모든 물체가 동적인 경우엔 추가적인 비용이 발생 할 수 있다는 점 정도라고 생각한다.
현재는 Mesh Shader를 통한 Meshlet 렌더링(인스턴싱, Cluster Culling, Backface Culling, Dynamic LOD를 모두 포함한) 파이프라인의 구현을 위해 시간을 사용하고 있기 때문에, 이들의 구현과 최적화 및 안정화가 완료되고 리팩터링 과정을 한번 끝내고 나서 처리하고자 한다. (이들의 구현 도중에 내부 구현 또한 변경 될 수 있기에)
2025.02.07 추가 노트
문제는 객체의 파괴다, 업데이트 감지와 파괴 모두 결국 전체 TransformComponent를 가진 엔티티를 순회해봐야지만 알 수 있기 때문이다. 회피 방법이 아에 없는건 아니다. 가장 간단한 방법은 두 타입의 Transform Component를 분리 시키는 것으로 생각된다. 만약 이를 구현하게 된다면 추가적인 아이디어를 생각해봐야 할 것.
'Pot of Thoughts > Fragments of Think' 카테고리의 다른 글
전통적인 그래픽스 파이프라인 vs 메쉬 셰이더 파이프라인(Meshlet Rendering) (0) | 2025.02.18 |
---|---|
Frames In Flight의 개념 (0) | 2025.02.06 |
언리얼에서 활용한 Skeletal Mesh를 Blender FBX Export를 할 때 (0) | 2024.11.23 |
D3D12 래퍼 설계에 대하여 #2 (0) | 2024.02.08 |
API 설계에 대한 사견 #3 (0) | 2024.02.08 |