메모리 최적화에 대한 이슈는 게임 개발자에게 굉장히 민감한 주제 중에 하나 입니다.
특히나 게임 개발에서 메모리를 가장 많이 차지하는 것은 텍스쳐입니다.
지금부터 언급되는 텍스쳐는 DirectX9 에서 POOL_MANAGED 옵션으로 생성되는
Managed-Texture 형태의 텍스쳐로 국한합니다.
( 텍스쳐 관리 시스템에서 제어되는 텍스쳐는 POOL_MANAGED 옵션을 가진 텍스쳐 뿐입니다. )
게임개발자가 텍스쳐를 사용하는 동작을 제가 아래와 같이 단순화 시켜보았습니다.
먼저 우리가 원하는 텍스쳐를 메모리에 로드할 수 있습니다.
비디오-메모리에 직접적으로 로드할 수 있으며, 시스템-메모리에 로드할 수도 있습니다.
로드할 때, 우리는 메모리를 조금 더 최적화하기 위해서
DirectX 에게 여러가지 힌트 옵션을 설정하기도 합니다.
이런 힌트 옵션들은 DirectX의 여러 버전들마다 설정 방법이 조금씩 다르지만,
기본적으로 내용은 비슷합니다.( 읽기전용/쓰기 전용 등이죠... )
그리고 필요해 위해서 DirectX 의 상태를 변경해서 텍스쳐를 그래픽 파이프라인에 연결합니다.
SetTexture( ... ) 같은 API 들이 이런 역할을 합니다.
마지막으로 필요하지 않는 텍스쳐는 직접 제거를 해줍니다.
결국 개발자가 할 수 있는 것은 Load-->Bind-->Release 의 형태 뿐입니다.
게임을 하는 중에 비디오-메모리가 가득차 있는데,
새로운 텍스쳐가 로드되면 어떤 상황이 벌어질까요?
새로운 텍스쳐를 로드하기 전에 비디오-메모리의 사용 가능한 공간을 체크한 후에,
새로운 텍스쳐를 로드하면 간단히 해결할 수 있습니다.
하지만 지금 우리는 개발할 때, 그런 체크를 하지 않습니다.
이 상황으로 유츄해 보면, 누군가에 의해서 자동적으로 어떤 작업이 이루어지고 있습니다.
그 누군가가 바로 DirectX의 텍스쳐 관리 시스템입니다.
게임 개발자에게 주어진 권한은 텍스쳐의 메모리의 생성과 제거 정도에 지나지 않습니다.
그 이외에 나머지 처리는 모두 DirectX의 텍스쳐 관리 시스템에 의해서 제어됩니다.
이 텍스쳐 관리 시스템은 DirectX6 부터 도입되었습니다.
즉, 이전까지는 아마도 개발자들이 직접 메모리를 계산하면서 처리했을 겁니다.
( 아마도~~ㅎㅎ )
이런 텍스쳐 관리 시스템은 기본적으로 다음과 같은 동작을 합니다.
- 현재 사용 가능한 텍스쳐 메모리의 양을 체크하고 추적
- 현재 렌더링 작업을 위해서 필요한 텍스쳐와 그렇지 않은 텍스쳐를 결정
- 비디오-메모리에 있는 텍스쳐 중에 어떤 것이 제거되어야 하는지를 결정
- 시스템-메모리에서 다시 읽어들일 텍스쳐를 결정
사실 이것보다 더 많은 작업을 하겠지만, 제가 이 정도로만 간략화 시켜보았습니다.
텍스쳐 관리 시스템은 여러 결정권을 가지고 있습니다.
특히나 비디오-메모리에서 제거 권한은 우리 개발자에게 정말 두려운 권한입니다.
힘들게(?) 비디오-메모리에 올려두었는데,
시스템에서 자기 마음대로(?) 그냥 내려버리면 얼마나 가슴 아프시겠습니까?
그래서 이런 가슴 아픈 일을 방지하는 차원에서
텍스쳐들에게 우선 순위를 부여할 수 있습니다.
텍스쳐 관리 시스템은 일정한 규칙을 가지고 비디오-메모리에서 텍스쳐를 제거합니다.
그 규칙이라는 것은 기본적으로 LRU( least-recently-used ) 에 기반을 두고 있습니다.
이는 비디오-메모리가 가득차 있는 상태라면,
이들 중에 비디오-메모리에서 제거가 되는 것은
가장 오랫동안 사용되지 않은 텍스쳐라는 것을 의미합니다.
개발자들은 비디오-메모리의 부족에 고통을 호소합니다.( 저만 그런가요? ㅎㅎ )
비디오-메모리가 가득찬 상태에서 새로운 하나의 텍스쳐가
비디오-메모리로 로드된다고 가정하겠습니다.
그러면, 텍스쳐 관리 시스템은 LRU로 체크를 해서
하나의 텍스쳐를 비디오-메모리에서 제거할 것입니다.
그리고 새로운 텍스쳐를 비디오-메모리로 로드할 것입니다.
실제로 텍스쳐 관리 시스템은 이것보다 훨씬 복잡한 방법으로 동작할 것입니다.
( 예를 들기 위해서 텍스쳐 크기 같은 문제를 제외하고 예를 드는 것 뿐입니다. )
이런 상황에서 10개의 새로운 텍스쳐가 비디오-메모리로 로드된다고 생각해 보십시오.
시스템-메모리에서 비디오-메모리의 데이터 전송이 최소 10번이 이루어져야 합니다.
또한 최소 10번 이상의 비디오-메모리에서 제거할 텍스쳐를 결정하는 작업이 이루어져야 합니다.
이것이 많아지면, 병목 현상을 초래할 수 있습니다.
이것은 텍스쳐 스레싱( texture thrashing ) 이라고들 합니다.
실제 게임 플레이 중인 상황에서 갑자기 새로운 배경이나 오브젝트들이 등장할 때
프레임이 느려지는 현상이 발생하게 되는데, 이런 작업도 거기에 영향을 미치는 요소입니다.
게임이 실행 중인 경우에는 이미 많은 비디오-메모리를 사용 중이니,
사실 빈번하게 발생할 수도 있는 일입니다.
무책임한 말일 수 있으나, 이것은 어쩔 수 없는 일입니다.
( 적절히(?) 개발자들이 노력해야 할 부분이죠? ㅎㅎㅎ )
그런데 비록 오래 사용되지는 않았지만,
무척이나 중요한 텍스쳐가 있으면 조금 문제가 되지 않을까요?
반드시 비디오-메모리에 있어야 할 텍스쳐라는 개념이 아닙니다.
( 만약 반드시 비디오-메모리에 있어야 한다면 POOL_DEFAULT 로 직접 관리해 주시면 됩니다.)
텍스쳐 관리 시스템에서 비디오-메모리에서 제거할 목록들을 구성할 때
일종의 특혜(?) 가 있는 텍스쳐들은 제거 목록에서 제외시켜달라는 의미로 볼 수 있습니다.
이것은 비디오-메모리에 계속해서 남아있을 필요는 없지만,
되도록이면 조금 더 오래 남겨달라는 의미로 해석하실 수 있습니다.
이렇게 특혜(?)를 부여하는 것이 바로 우선 순위를 설정하는 작업입니다.
비디오-메모리에서 제거될 후보군들 중에서 LRU 값이 같다면,
우선 순위가 낮은 텍스쳐가 제거되는 것입니다.
IDirect3DResource9 라는 인터페이스를 상속받아서
Direct3D의 텍스쳐 인터페이스가 만들어졌습니다.
IDirect3DResource9 인터페이스는 이 우선 순위를 설정할 수 있는 멤버함수를 가지고 있습니다.
IDirect3DResource9::SetPriority( ... ) 가 바로 그것입니다.
Managed-Texture 의 경우 기본적으로 우선 순위가 0 입니다.
0보다 높은 숫자를 입력하면, 높은 우선 순위가 됩니다.
그런데 무턱되고 높은 숫자를 입력하면, 문제의 소지가 있을 수 있습니다.
그래서 이를 상수로써 정해진 몇몇 값들이 있습니다.
그 값들은 아래와 같습니다.
D3D9_RESOURCE_PRIORITY_MINIMUM 0x28000000
D3D9_RESOURCE_PRIORITY_LOW 0x50000000
D3D9_RESOURCE_PRIORITY_NORMAL 0x78000000
D3D9_RESOURCE_PRIORITY_HIGH 0xa0000000
D3D9_RESOURCE_PRIORITY_MAXIMUM 0xc8000000
이것은 DirectX가 스케쥴링 목적으로 정의한 상수들입니다.
이 우선 순위를 그대로 텍스쳐에 적용하시면 안됩니다.
일반적으로 텍스쳐의 경우 D3D9_RESOURCE_PRIORITY_NORMAL 우선 순위를 사용해야 합니다.
그리고 아래와 같이 설정하는 것이 좋습니다.
ManagedTex->SetPriority( D3D_RESOURCE_PRIORITY_NORMA + 1 );
ManagedTex->SetPriority( D3D_RESOURCE_PRIORITY_NORMA + 2 );
ManagedTex->SetPriority( D3D_RESOURCE_PRIORITY_NORMA + 3 );
Managed-Texture는 실제로 렌더링 작업이 이루어지기 전까지
비디오-메모리에 데이터가 존재하지 않습니다.
렌더링 작업이 한번이라도 이루어져야 할 때,
시스템-메모리의 텍스쳐 메모리가 비디오-메모리로 전송이 됩니다.
이것도 바로 텍스쳐 관리 시스템이 하는 작업 중에 하나입니다.
그런데 텍스쳐를 로딩하고, 이를 비디오-메모리에 바로 올리고 싶은 경우가 있을 수 있습니다.
이미 텍스쳐를 로딩한다는 것 자체가,
바로 렌더링 작업에 사용할 것이라는 확신이 있는 경우일 것입니다.
그 때는 IDirect3DResource9::PreLoad() 를 사용하시면 됩니다.
그러면 약간의 성능 향상을 보일 수도 있습니다.
( 이것은 저도 아주 약간의 효과를 경험했었습니다.^^ )
게임을 플레이 중에 씬 전체가 새로운 텍스쳐들로 가득 채워져야 하는 경우는 없으셨나요?
그런 경우 텍스쳐 스레싱 현상이 아주 크게 느껴질 수도 있습니다.
그래서 한꺼번에 비디오-메모리를 모두 비워버리는 API가 있습니다.
IDirect3DDevice9::EvictManagedResource() 가 바로 그것인데,
이 API는 보시다시피 Device 인터페이스의 API 입니다.
즉, 버퍼와 텍스쳐를 포함한 Managed 리소스까지 모두 비디오-메모리를 비워버립니다..
그래서 씬이 완전히 새롭게 구성될 때, 꽤 유용할 수 있습니다.
( 저는 이 API 아주 좋아합니다..ㅎㅎㅎ )
DirectX9 의 텍스쳐 관리 시스템은 사실상 이 세가지 API를 통해서 제어를 합니다.
나머지는 우리 개발자의 손을 떠난 문제입니다.
위의 문제를 고민하기 전까지 저는 게임에 사용되는 메모리를 계산해 본 적이 없었습니다.
예를 들면 상점이나 특정 지역에서 사용되는 메모리 계산이겠죠.
게임 프로그래머가 단순히 주어진 리소스를 렌더링만 한다는 개념에서
벗어나게 해주었던 작업들이였습니다.