[App 개발] 수박 겉핥기 MacMAME 분석 - 1. 디스플레이
본문
Multiple Arcade Machine Emulator 의 약자인 MAME 는 게임 센터의 PCB 게임들의 에뮬레이터로서 고전 게임 매니아들에게 사랑받는 프로그램입니다. 대부분 기종에 이식되어 있고, 매킨토시에도 물론 있습니다. 68K, OS 9 용도 있습니다만, 요즘에는 Xcode 에서 컴파일이 될 수 있도록 변환작업을 하고 있다고 합니다. (인라인 어셈블리 구문이 CodeWarrior 와 차이가 있어서 애를 먹고 있다고 하는군요)
저는 MAME 를 통째로 다 들춰낼 생각은 없고, 그럴 능력도 없습니다. 많은 분들의 관심은 아마 “기존에 제작된 게임 소스들은 어떤 기법을 사용하고 있을까?”, 이 문제가 아닐까 싶습니다. 대부분 기본적인 문제들을 어떻게 풀어야 할 것인가에 대해서는 아이디어를 다 갖고 계시지만, 실제 구현된 모습을 보고 참고를 삼는 것도 나쁘지 않겠죠.
MAME 는 완전 소스가 공개된 아주 좋은 학습 재료일 것 같습니다.
맥마메는 완전히 카보나이즈 되어 있습니다. 하지만, 저는 소스를 한 줄 한 줄 분석하려고 하는 것이 아니라, 사용된 기법을 중심으로 살펴볼 것이기 때문에 아이디어를 쉽게 코코아로 이식하는 것도 어렵지 않을 것입니다.
주의: 저는 마메를 몽땅 분석한 적이 없고, 그럴 맘도 없습니다. 따라서 제가 확인한 내용들이 틀릴 수도 있습니다. 혹시 알고 계신 내용과 다른 부분이 있으시면 지체 말고 알려주시면 감사하겠습니다. 저도 배우면서 하는 거니까요. ^^
1. 디스플레이
기존의 맥마메에 비해서 Mac OS X 용 마메가 가지는 새로운 특징은 OpenGL을 지원한다는 점일 것입니다. 물론 소프트웨어 화면 덤프 기능도 지원합니다. GL 드라이버의 속도가 시원치 않은 옛날 기종들을 위한 배려지요.
저는 먼저 OpenGL 을 이용한 화면 덤프를 먼저 살펴보려고 합니다. 소프트웨어 덤프도 흥미롭긴 하지만, 사실 이제 게임 엔진의 대세는 GL 로 넘어갔다고 봐도 틀림이 없어보이는 상황이니까요.
1) 소스의 위치
맥마메 소스를 다운 받으셔서 보시면 override 폴더 내에 macintosh 라는 폴더가 있습니다. 이것이 맥마메 쉘입니다. 메인 함수는 여기 있습니다. 저기 src 폴더 가셔서 ‘메인 함수가 어디 있지?’ 하고 두리번 거리셔도 소용이 없습니다.
그 중에서 OpenGL 용 루틴들이 위치한 곳은 OpenGL 폴더입니다. 두 개의 소스 파일이 있는데요, 그 중에서 OpenGLPluginX.c 를 살펴보시면 중요한 루틴들을 모두 다 살펴보실 수 있을 것입니다.
2) 화면 업데이트
게임 디스플레이의 핵심은 바로 이 실시간 화면 업데이트에 달려있다고 해도 과언이 아닙니다. 시간이 있으신 분은 macintosh 폴더에 있는 software.c 를 살펴보시면 거기에는 다양한 화면 덤프 루틴의 최적화된 구현이 수록되어 있습니다. 소스 비트 대 화면 비트에 따라서 덤프 루틴의 구현이 달라지고, AltiVec 을 지원할 것인가 말 것인가에 따라서도 역시 구현이 달라져야 합니다. 맥마메에서는 함수 포인터를 사용하여 이런 다양한 덤프 루틴 중 하나를 매핑하여 화면 업데이트 루틴에 합류시키는 방법으로 구현됩니다. 그것이 opengl_blit_table[] 함수 포인터 배열입니다.
DoBlit() 함수의 바로 위를 보시면 여덟 개의 함수들이 배열로 선언됩니다.
static DECLARE_BLIT_FUNC((*opengl_blit_table[8])) =
{
//16 bit blits
blit_1x1_full_16to16d, //direct
blit_1x1_full_16to16l, //lookup table
blit_1x1_full_16to16d_vec, //direct altivec
blit_1x1_full_16to16l_vec, //lookup altivec
//32 bit blits
blit_1x1_full_32to32d,
blit_1x1_full_16to32l,
blit_1x1_full_32to32d_vec,
blit_1x1_full_16to32l_vec
};
처음의 네 함수들이 16비트 덤프, 다음의 네 함수는 32비트 덤프입니다. 그리고 각각 처음 것은 RGB, 다음 것은 look-up-table 을 사용하는 화면 분포인 것으로 보입니다. 그 다음에는 AltiVec 을 사용하여 최적화된 덤프 루틴이 자리잡고 있습니다.
DoBlit() 에서는 현재 진행되는 게임 환경에 따라서 적절한 덤프 루틴을 선택하여 실제 메모리 덤프를 수행합니다.
변수 srcpixelsize 가 2 로 정의되어 있으니, 기본 값은 16비트가 되겠군요. 변수 key 가 바로 함수 포인터 배열의 인덱스입니다. 구조체 inParams 에 화면 구성 인수들이 저장되어 있는 것 같습니다. 그래서 화면의 넓이 높이, 그리고 게임 화면의 시작 위치 옵셋값을 내부 변수로 먼저 옮깁니다.
그 다음에는 bit depth 가 32인지를 확인하여 변수 scrpixelsize 를 4로 바꾸어줍니다. 역시 이것은 도트 한 개당 바이트 수를 의미하는 것이 되겠습니다. 그래서 depth 가 32라면 32비트 덤프 루틴을 호출하여야 하기 때문에 인덱스 key 값에 4를 더하는군요. 그 다음 look-up-table 을 이용하는 게임일 경우 key 값을 1 더합니다.
if ((inParams->depth == 32) && (0 == inParams->lookup32))
srcpixelsize=4;
if (inParams->depth == 32)
{
key +=4;
if (inParams->lookup32)
key+=1;
}
else if (inParams->lookup16)
key+=1;
그 다음 소스의 수평 바이트 수, 그리고 전체 바이트 수를 계산해 냅니다.
srcRB = inParams->rowbytes;
src = (UInt8 *)inParams->bits + yoffset * srcRB + srcpixelsize * xoffset;
마메의 경우는 각각의 게임 PCB 마다 서로 다른 화면 구성을 갖고 있기 때문에 이러한 절차가 반드시 필요할 것으로 보입니다. 피씨 게임처럼 320 x 240, 혹은 640 x 480 같은 족보를 따를 이유가 전혀 없기 때문이죠.
그 다음 AltiVec 을 지원하는 컴퓨터인지 플래그를 확인하고, 소스 화면 버퍼의 크기가 AltiVec 을 사용하기에 적당한 놈인지를 확인하는 절차입니다. src & 0xF 를 해서 0이 되는지를 확인하고 있군요. 따라서 화면 버퍼 포인터의 시작 위치가 256, 512, 768, 1024, … 이런 식으로 나가는 놈은 AltiVec 을 적용하도록 프로그램되어 있습니다. 그렇게 선택된 덤프 루틴으로 실제 덤프가 수행됩니다. 덤프는 소스 버퍼에서 화면 버퍼로 수행됩니다. 화면 버퍼는 맥마메의 화면 크기가 되겠지요. 640x480, 800x600…
if ((gHasAltivec) && (0 == ( (unsigned long)src & 0xF)))
{
key +=2;
}
(*opengl_blit_table[key])(src, ctx->screenTexture.buffer, srcRB,
ctx->screenTexture.rowbytes, width, height, inParams);
3) OpenGL 화면 덤프
그 다음, 우리가 눈여겨보아야 할 중요한 기법이 나옵니다. OpenGL 을 이용하여 전체 화면 덤프를 구성할 때 응용되는 방법으로 알려져 있는데, 맥마메에서 실제 사용되고 있군요.
일반적인 OpenGL 의 텍스쳐는 64 x 64, 128 x 128, 256 x 256… 이렇게 나간다고 배웠습니다. (NeHe Tutorial 을 참조하세요. ^^) 그런데, glTexSubImage2D() 함수를 이용하면 반드시 정사각형이 아니어도 텍스쳐를 만들 수 있습니다. 거두절미하고 소스를 보시죠.
glPixelStorei(GL_UNPACK_ROW_LENGTH, c->screenTexture.w);
glTexSubImage2D(c->screenTexture.target, 0, 0, 0, width, height,
c->screenTexture.format, c->screenTexture.type, c->screenTexture.buffer);
이렇게 됩니다. 함수의 용법과 인수는 다른 문서들을 참조하십시오. 저도 사실 이렇게 된다… 를 애플 디벨로퍼 샘플 코드를 보고 힌트를 얻었습니다. 그 소스는 코코아로 되어 있었는데요, 그것을 응용하여 카본으로 1600x1200 화면 업데이트 테스트를 만들어본 적이 있습니다. 메모리 덤프에 아무런 최적화도 가하지 않은 상태였는데도 상당히 빠른 속도의 화면 업데이트가 가능했습니다.
OpenGL 화면 덤프의 인터페이스 함수는 UpdateDisplay() 입니다. 그 곳에서 먼저 glClear() 가 호출되고 그 다음 DoBlit() 함수가 실행됩니다. 만약 오버레이 화면이 더해질 경우에는 glMultiTexCoord2f() 함수가 호출되고, 게임 화면만 출력될 때에는 glTexCoord2f() 가 호출됩니다. glTexCoord2fv() 는 텍스쳐 좌표가 벡터로 입력되는 함수입니다.
특이한 점은 일반적인 OpenGL 텍스쳐의 텍스쳐 좌표는 0 부터 1 사이가 되고, 1부터 2 로 넘어가면 0부터 1이 반복됩니다만, 여기서 사용되는 서브 이미지 텍스쳐는 텍스쳐 좌표가 실제 픽셀 좌표입니다. 무척 편하죠. 화면 좌표는 -1부터 1까지이니까, 이렇게 하면 화면의 확대 축소도 누워서 떡먹기입니다. 우왕~
물론 맥마메에는 이외에도 엄청 많은 루틴들이 숨어 있지만, 그거야 마메의 다양한 configuration 에 대응하기 위한 것이기 때문에 다 알아야 할 필요가 없는 것 같군요. AltiVec 에 대해서는 아직 분석이 미진한 부분이 있습니다. 좀 더 살펴보고 중요한 내용이 있으면 업데이트 하도록 하겠습니다.
최신글이 없습니다.
최신글이 없습니다.
댓글목록 0