멀티스레드 기술을 적용한 가상 3D 게임 엔진에서의 병렬 처리 기법 김문순, 노성남, 박제훈, 박건수, 김은주, 김선정, 송창근 한림대학교 컴퓨터공학과 e-mail : {sunfire99,customizer,irubeta01,20045163,ejkim628,sunkim,cgsong}@hallym.ac.kr Parallel Processing Technique in Virtual 3D Game Engine That Apply Multi-Thread Mun-soon Kim,Sung-nam No,Je-hun Park,Gun-soo Park,Eun-Ju Kim,Sun Jeong Kim,Chang Geun Song Dept of Computer Engineering, Hallym University 요 약 본 논문은3D 게임 엔진 제작시 듀얼 코어급 이상의 CPU를 탑재한 플랫폼에서 게임의 처리 속도를 향상시키기 위해 멀티 코어에 물리적 작업 스레드 기술을 적용한다. 게임 플레이에 적용되는 큰 모듈의 게임을 데이터 처리부와 렌더링 처리부의 두 단계로 나누어 멀티 스레드 기술이 적용된 가상 3D 게임 엔진에서의 병렬 처리기법을 설계한다. 1
2009년도 한국멀티미디어학회 춘계학술발표대회 논문집 12권1호 1. 서론 게임을 개발하는 과정에서 게임 엔진은 자동차의 엔진과 같은 역할이다. 자동차의 가치를 평가하는 요소 중에 어떤 엔진을 적용 했는가는 자동차를 평가하는데 중요하다. 게임도 게임 개발 기술 중 엔진이 중요한 위치를 차지한다. MiddleWare와 FrameWork는 하드웨어(OS)와 어플리케이션(게임) 사이의 원활한 의사 소통을 위한 소프트웨어이다. 소프트웨어적 개념의 FrameWork와 하드웨어적 개념의 추상적 의미인 MiddleWare의 두 종류를 동일시 해서 생겨난 키워드가 게임 엔진이다. 결국 게임 엔진은 하드웨어와 밀접한 관계가 있기 때문에 그만큼 하드웨어에 많은 영향을 받게 된다. 게임 엔진에서 하드웨어는 하드웨어의 기능을 최대한 활용하여 높은 처리 효율을 얻는데 있다. 멀티스레드를 적용한 게임 엔진으로는 일본 Capcom사의 자체 개발 엔진인 M.T FrameWork[5]와 미국 Epic사의 Unreal3엔진[6]이 대표적이다. 하지만 두 엔진들은 철저히 보안되어 있어 자체 기술을 습득하기란 쉽지 않다. 본 논문은 듀얼 코어급 이상의 CPU를 탑재한 시스템에 2개 이상의 물리적 코어를 활용하여 게임 엔진의 주요 모듈들을 멀티스레드화 하여 병렬 처리 하고자 한다. 2. 본론 2.2 Thread & Thread Manager 보통 한 개의 프로그램을 실행 시키면 메인 메모리에 CPU시간을 할당 받은 하나의 프로세스가 상주하게 된다. 프로세스가 하나 생성 될 때 Thread 하나가 같이 생성이 된다. 그리고 생성된 스레드가 프로세스 밑에서 실제적으로 작업을 하게 된다. 하나의 프로세스에는 여러 개의 스레드를 만들어 사용할 수 있으며, 이 스레드들을 사용자가 CPU의 각 코어에 지정하여 특정 코어에만 종속시켜 작업 할 수 있다. 본 논문은 각 모듈에 스레드를 적용하고, 그 스레드들을 각 코에 할당 시켜 멀티스레드 환경을 구축한다. 각 모듈에 스레드를 적용하는 방법으로 스레드를 객체화 하였다. 가장 먼저 Primary Thread가 하나 생성되면 Main Thread가 Primary Thread로 작업을 하게 된다. 그리고 사용자가 직접 커널에게 커널 오브젝트 생성을 요구하는 시스템 함수인 CreateThread()나 _beginthreadex() 함수를 호출하여 Runnable Thread를 생성한다. 결론적으로 제안한 게임 엔진상에서 GameFrameWork가 MainThread에 의해 작업을 하게 되고, Renderer는 RunnableThread에 의해 작업을 하게 된다. 스레드를 구분하였다면 각 스레드를 각각 할당한다. CPU의 코어에 CPU의 코어들은 특정한 Mask값을 가지고 있다. 그 Mask값들을 AffinityMask라 부르며. 이 값들은 그림2에서 보인 것처럼 16진수 형태로 표현된다. Core Affinity Mask 0 Core 0x00000001L 1 Core (그림 1) 전체적인 0x00000002L 구성도 2 Core 0x00000004L 3 Core 0x00000008L 2.1 Module Divide (그림 2) Core의 AffinityMask 값 CPU의 코어 개수는 SYSTEM_INFO라는 구조체 내의 dwnumberofprocessors멤버변수가 처리하고, 코어에 대한 정보는 SetThreadAffinityMask() 함수를 호출하여 스레드를 할당한다. 멀티스레드 환경을 구축하기 위해 제일 먼저 어떤 모듈을 병렬화 시킬 것인지 작업의 Target을 정한다. Target은 데이터 연산을 위한 모듈과 연산된 데이터를 렌더링 하기 위한 모듈로 나눈다. 이하 각 모듈을 GameFrameWork 와 Renderer"라 부른다. (그림 3) SetThreadAffinityMask 함수원형 2
그림 3에서 첫 번째 인자로 스레드의 핸들값을 받고, 두 번째 인자로 코어 AffinityMask값을 받는다. 만약 0번 코어에 스레드를 할당하고 싶다면, 첫 번째 인자에 사용할 스레드 핸들을 넣어 주고, 두 번째 인자에는 0x00000001L이라는 AffinityMask값을 넣어 주면 된다. 스레드를 코어에 할당하는 것은 정해진 함수만 호출하는 식으로 행해지기 때문에 비교적 간단하게 처리할 수 있다. (그림 4) Thread / Thread Manager class Diagram ThreadManager class를 통하여 객체화한 스레드를 관리하도록 하였다. Thread class들을 컨테이너에 저장 및 등록하거나 해지 등의 기능을 수행하게 된다. 그림 4은 앞에서 설명한 스레드를 객체화 시키고 관리하는 ThreadManager에 관한 클래스 다이어그램이다. 스레드 객체들은 ThreadManager에 종속되어 관리된다. (그림 5) Synchronize Object class Diagram Synchronize Object는 유저 모드에서의 동기화 오브젝트와 커널 모드에서의 동기화 오브젝트로 나뉘는데 먼저 유저 모드의 동기화 오브젝트에는 Critical-Section이 있고, 커널 모드에서의 동기화 오브젝트에는 Event, Semaphore, Mutex가 있다. 일반적으로 응용 프로그램은 유저 모드에서 실행이 되므로 Critical-Section을 많이 사용 한다. 그림5는 동기화 오브젝트 객체간의 상관 관계를 클래스 다이어그램으로 나타낸 것이다. 동기화 오브젝트들은 팩토리에 의해 생성되며 객체 접근은 인터페이스로 한다. 2.4 Circular Queue 2.3 Synchronize Object Synchronize Object는 공유된 메모리에 둘 이상의 스레드가 동시에 접근할 때 생기는 문제를 해결하기 위해 사용된다. 하나의 스레드가 공유되는 메모리에 접근하는 동안 다른 스레드들이 접근하지 못하도록 한다. 스레드들이 스택 메모리 공간 이외의 메모리 공간을 서로 공유하기 때문에 발생하는 문제를 해결하기 위해서 필요한 것이다. 멀티스레드 환경을 위해 GameFrameWork와 Renderer를 Thread Safe한 환경으로 개발하였다. Thread Safe를 위해 두 모듈 사이에 단방향 구조인 원형 큐(링 버퍼) 메모리 버퍼를 적용하였다. 원형 큐 메모리 버퍼는 데이터를 읽고 쓰는 인터페이스가 필요하다. 제안한 엔진의에서 GameFrameWork와 Renderer는 단방향의 구조를 갖는다. GameFrameWork에서 데이터를 쓰고 나면, 쓰여진 데이터를 Renderer에서 읽어 들여 렌더링 하게 된다. 2.5 Data Instruction 3 Data Instruction은 GameFrameWork와 Renderer사이의 작업 단위이다. 원형 큐에 GameFrameWork에 의해서 쓰여지고 Renderer에 의해서 읽어 들여지는 것이 Data Instruction이다. GameFrameWork에서 처리한 데이터들은 다양한 자료형의 변수들로 자료형들은 작업을 하기 위해서
2009년도 한국멀티미디어학회 춘계학술발표대회 논문집 12권1호 관련 있는 것끼리 묶이게 된다. 개체 수가 정확히 정해지지 않은 변수들을 처리하기 위해서 Data Instruction을 class형태로 만들어 원형큐에 작업단위로 넘기게 된다. Data Instruction의 변수들은 그 개체수가 정해져 있지 않기 때문에 Data Instruction의 인터페이스들을 그 개수에 맞게 정해준다. 0개의 변수를 받아 처리하는 Data Instruction부터 n개의 변수를 받아 처리하는 Data Instruction까지 인터페이스를 만들어두고, 그 개수에 맞는 Data Instruction을 찾아서 사용하게 만드는 것이다. 그림 6와 그림 7은 멀티스레드가 적용된 엔진을 이용하여 제작된 게임이다. 그림6에서 4개의 코어 중 2개의 코어만 사용되고 있는 것을 확인 할 수 있다. (그림 7) Engine Sample Prototype_2 3. 결론 (그림 6) Engine Sample Prototype_1 멀티스레드 기반의 병렬처리 기법을 가상 3D게임 엔진에 적용하여 3D 게임의 처리 속도를 향상 시키고자 하였다. 본 논문은 게임 엔진의 전 부분을 제작하기 보다 게임 엔진의 여러 구성 요소들 중에서 멀티스레드 기법을 이용하여 두 모듈간의 데이터 연산 처리와 렌더링을 병렬처리화하는 방법만을 적용하였다. GameFrameWork의 스레드와 Renderer의 스레드를 싱글 코어 모드에서 진행했을 때와 두 스레드를 병렬화하여 듀얼 코어 모드에서 진행했을 때의 초당 프레임 단위가 10~20fps정도로 차이가 생겼다. 하지만 게임에서 fps란 상대적인 개념의 숫자이다. 본 논문에서 실험한 Sample의 fps는 듀얼 코어 모드로 진행했을 때 두 스레드에서 각각 90~100fps정도의 프레임 수가 나온다. 싱글 코어 모드로 진행했을 때는 70~80fps 로 10~20fps의 차이는 현 fps가 3000fps로 나온다고 환산했을 때 300~600fps의 차이가 수치적인 계산 된다. fps가 낮을수록 수치 1이란 굉장히 큰 의미를 갖게 된다. 본 논문은Renderer스레드 부분에 많은 비중을 두었다. 향후 Renderer스레드에서의 일부 연산들을 GameFrameWork스레드로 전환한다면 더 많은 처리 효율를 기대 할 수 있을 것이다. ACKNOWLEDGMENTS 본 연구는 지방 대학 혁신 역량 강화 사업(NURI) 문화 컨텐츠(CT) 인력 양성 사업의 5차년도 연구비 지원으로 수행되었습니다. 4
참고문헌 [1] 윤성우, TCP/IP 소켓 프로그래밍, FREELEC, 2007 [2] 로버트C. 마틴, Java 프로그래머를 위한 UML 실전에서는 이것만 쓴다., 인사이트, 2005 [3] Multithreaded Game Engine Architectures, Gamasutra, http://www.gamasutra.com/view/feature/1830/mult ithreaded_game_engine_.php?print=1 [4] http://minjang.egloos.com/1665441 [5]http://cafe.naver.com/game169.cafe?iframe_url =/ArticleRead.nhn%3Farticleid=31 [6]http://www.gpgstudy.com/gpgiki/ 5