에뮬레이터
fd9dc5c 기준이며, 정식 릴리스 0.10.5와 개발 중인 0.11.0의 차이도 짚습니다.
mGBA는 게임기의 부품과 그 부품이 움직이는 순서를 코드로 구현한 프로그램입니다. 게임 파일에 들어 있는 기계어를 해석하고, 가상 메모리를 읽고 쓰며, 화면과 소리를 만들어 컴퓨터의 출력 장치로 전달합니다. 에뮬레이터의 핵심은 그림을 비슷하게 그리는 것보다, 하드웨어의 상태가 언제 바뀌는지 재현하는 데 있습니다.
ROM은 대본이고, CPU는 다음 지시를 읽는 배우입니다. 메모리는 소품 창고이며, 화면과 소리는 관객이 보는 결과입니다.
대본 내용만 맞아도 공연이 완성되지는 않습니다.
조명이 켜질 때와 음악이 나올 때를 맞추는 무대감독이 필요합니다.
mGBA에서는 mTiming이 그 시간을 관리합니다.
src/core/core.c의 mCoreFindVF()가 파일 내용을 판별해 GBA 또는 GB 코어를 고릅니다.GBACoreCreate()가 공통 함수표를 채우고, init 호출이 CPU·보드·렌더러를 준비합니다.reset을 호출합니다.이 흐름은 src/platform/example/client-server/server.c의 _mExampleRun()에서 특히 잘 보입니다.
입력을 받아 setKeys를 호출하고, runFrame 뒤에 화면 버퍼를 보냅니다.
이 예제의 네트워크 연결은 화면 전송 학습용이며, 게임기의 통신 케이블을 인터넷으로 구현했다는 뜻은 아닙니다.
함수 하나의 정답보다 누가 상태를 소유하고, 누가 호출하며, 어느 시점에 결과가 보이는가를 배웁니다. 이 질문은 게임 엔진, 오디오 프로그램, 장치 시뮬레이터에도 그대로 적용됩니다.
2026-10-23 분석 대상 선정 기준은 TrendShift Repositories #2입니다. 같은 날 GitHub 기준 스타는 7,521개, 포크는 1,139개입니다. 다만 이 수치와 순위만으로 특정 영상·사건·기능이 유행의 원인이라고 단정할 수는 없습니다. 여기서는 소스와 문서에서 드러나는 학습 가치에 초점을 맞춥니다.
첫째, 하나의 엔진을 여러 제품 형태로 재사용합니다. Qt GUI, SDL 프론트엔드, Libretro 연동과 Python 바인딩이 공통 코어 주변에 놓입니다. 둘째, CPU뿐 아니라 센서·저장·영상·디버거까지 이어져, 낮은 수준의 구현이 사용자 기능으로 바뀌는 과정을 볼 수 있습니다.
셋째, 오래된 기계를 다루면서도 이식성과 유지보수를 계속 다룹니다.
CHANGES의 개발 버전 항목에는 Qt 6 지원, DMA 경계 동작 수정, 헤드리스 프론트엔드 등이 있습니다.
이는 분석 커밋의 변경 기록이며, 모두 0.10.5에 들어 있다는 의미는 아닙니다.
| 도구 | 공식 자료상 중심 | mGBA를 공부할 때의 차별점 |
|---|---|---|
| mGBA | GBA와 GB·GBC, Qt·SDL, 디버깅·스크립트 | C의 공통 코어 계약에서 CPU·주변장치·GUI까지 추적하기 좋음 |
| VBA-M | GB·GBA 에뮬레이터. wxWidgets 및 SDL 포트 | mGBA에서는 Qt와 mCore의 경계를 중심으로 같은 문제의 다른 설계를 배움 |
| SameBoy | GB·GBC·Super Game Boy, 정확도와 디버깅 기능 | GBA의 ARM·Thumb까지 함께 공부하려면 mGBA의 대상 범위가 알맞음 |
비교 근거: VBA-M 공식 저장소, SameBoy 공식 소개. 지원 범위와 구성 방식의 비교이며, 같은 조건의 성능·정확도 측정 결과는 아닙니다.
mGBA를 공부할 가치는 여러 화면과 플랫폼이 같은 에뮬레이션 엔진을 공유하도록 만든 경계에서 드러납니다. SDL로 바꾸어도 ARM 명령어의 의미를 다시 구현하지 않는 이유를 이해하는 것이 좋은 출발점입니다.
| README 표현 | 소스에서 확인할 근거 | 해석할 때 주의할 점 |
|---|---|---|
| 빠르고 정확한 에뮬레이션 | 사이클 계산, 메모리 대기 시간, 하드웨어 테스트 자료 | 설계 방향을 뒷받침하지만 경쟁 도구보다 항상 빠르다는 증거는 아님 |
| 되감기·상태 저장 | src/core/rewind.c, src/gba/serialize.c | 화면만 저장하는 기능이 아니라 가상 기계의 상태 복원이 필요함 |
| 통신 케이블 지원 | README는 같은 컴퓨터에서의 연결을 명시 | 네트워크 멀티플레이는 README의 계획 항목에 있으므로 구분해야 함 |
좋은 교육용 건축물은 겉모습만 멋진 건물이 아닙니다. 배관·전기·기둥이 어디서 연결되는지 읽을 수 있어야 합니다. mGBA의 공통 코어와 플랫폼 폴더는 그 연결부를 살펴볼 수 있게 해 줍니다.
웹 서비스의 용어를 빌리면 백엔드는 로컬 에뮬레이션 코어, 프론트엔드는 Qt·SDL 화면입니다. 일반 실행 경로에 HTTP 서버나 클라우드 DB가 필요한 구조는 아닙니다. SQLite도 사용자 계정 서버가 아니라 게임 데이터베이스 기능에 쓰는 선택 의존성입니다.
| 영역 | 실제 파일·설정 | 배울 수 있는 기술 |
|---|---|---|
| 언어 기준 | CMakeLists.txt: CMAKE_C_STANDARD 11 | 고정 폭 정수, 구조체, 함수 포인터, 조건부 컴파일 |
| GBA CPU | src/arm/arm.c, src/arm/isa-arm.c, src/arm/isa-thumb.c | ARM·Thumb 명령 디코딩과 실행, 파이프라인 상태 |
| GB CPU | src/sm83/, src/gb/ | 다른 CPU와 하드웨어를 같은 코어 인터페이스로 감싸기 |
| 주변장치 | src/gba/memory.c, src/gba/video.c, src/gba/audio.c, src/gba/dma.c | 주소 매핑, 영상 타이밍, 샘플 생성, 메모리 전송 |
| 공통 기능 | src/core/, src/util/ | 스레드, 상태 저장, 가상 파일, 순환 버퍼 |
src/platform/qt/CMakeLists.txt는 C++14를 요구하며,
QT_VERSIONS 6 5 순서로 Qt를 탐색합니다.
Core·Widgets·Network를 찾고 Multimedia는 선택 모듈로 취급합니다.
FORCE_QT_VERSION에 5 또는 6을 지정하는 분기도 있습니다.
SDL은 src/platform/sdl/CMakeLists.txt에서 SDL_VERSION "3"이 기본입니다.
SDL 3을 찾지 못하면 2, 그다음 1.2를 찾습니다.
이는 버전 번호를 하나로 고정한 잠금 파일이 아니라 사용 가능한 라이브러리를 선택하는 탐색 정책입니다.
| 항목 | 소스에 기록된 값 | 근거와 의미 |
|---|---|---|
| CMake | 3.12 이상 | 루트 CMakeLists.txt 첫 줄의 실제 최소 요구 |
| 개발 버전 | 0.11.0 · ABI 0.11 | version.cmake. 정식 릴리스 번호와 구분 |
| 동봉 zlib | 1.2.12 | src/third-party/zlib/zlib.h의 ZLIB_VERSION |
| 동봉 libpng | 1.6.51 | src/third-party/libpng/png.h의 버전 매크로 |
| 동봉 SQLite | 3.51.1 | src/third-party/sqlite3/sqlite3.h의 SQLITE_VERSION |
| Python CFFI | 1.6 이상 | src/platform/python/setup.py: cffi>=1.6 |
| Python 이미지 확장 | Pillow 2.3 이상 | 같은 파일의 pil 선택 의존성. pytest는 테스트용 |
| 그 밖의 기능 | FFmpeg·libzip·Lua·json-c·libedit 등 | CMake의 find_feature()로 탐색. 하나의 설치 버전으로 고정되지 않음 |
동봉 버전은 저장소 안의 사본에 대한 값입니다. CMake가 시스템 라이브러리를 찾는 경우 실제 링크 대상 버전은 달라질 수 있습니다. FFmpeg는 녹화, libpng·zlib는 이미지와 압축, libzip은 압축 ROM, Lua는 스크립트 기능과 연결됩니다.
# CMakeLists.txt의 기본값 일부를 간추린 구성 지도
M_CORE_GBA=ON M_CORE_GB=ON
BUILD_QT=ON BUILD_SDL=ON
BUILD_SHARED=ON BUILD_STATIC=OFF
BUILD_LIBRETRO=OFF BUILD_PYTHON=OFF
BUILD_SUITE=OFF BUILD_CINEMA=OFF
ON은 기능을 요청하는 기본값이지, 모든 환경에서 반드시 포함된다는 보장은 아닙니다.
Qt·SDL 라이브러리를 찾지 못하면 해당 프론트엔드가 제외될 수 있습니다.
LIBMGBA_ONLY 분기는 프론트엔드를 끄고 정적 라이브러리 중심으로 구성합니다.
CMakePresets.json의 clang·gcc 프리셋은 Ninja, Debug,
CMAKE_EXPORT_COMPILE_COMMANDS=ON을 설정합니다.
이름만 보고 컴파일러 실행 파일까지 고정한다고 해석하면 안 됩니다.
테스트 구성은 src/platform/test/CMakeLists.txt에 있습니다.
BUILD_SUITE는 cmocka 기반 단위 테스트, BUILD_CINEMA는 화면 결과 비교 도구를 만듭니다.
README는 외부 Buildbot 배지와 Docker 이미지를 안내하지만, 이 커밋의 프로젝트 .github/에는 후원 설정만 있으며 GitHub Actions 워크플로는 없습니다.
서드파티 libpng의 워크플로를 mGBA 자체 CI 설정과 혼동하지 않아야 합니다.
README는 CMake 3.1 이상, GUI 의존성 Qt 5, SDL 2 권장을 안내합니다. 분석 커밋은 CMake 3.12 이상을 요구하고 Qt 6·5 탐색과 SDL 3·2·1.2 탐색을 구현합니다. 빌드 환경을 정할 때 이 차이가 실제 오류 원인이 될 수 있습니다.
먼저 두 종류의 시간을 구분합시다. 가상 게임기의 시간은 CPU 사이클로 흐르고, 실제 컴퓨터의 시간은 화면 갱신과 오디오 장치의 소비 속도로 흐릅니다. 코어는 전자를 계산하고, 동기화 계층은 후자와 보조를 맞춥니다.
연결선은 주된 데이터·제어 경로를 요약합니다. BIOS와 게임 저장 파일은 각각 별도의 loadBIOS·loadSave 호출로 연결합니다. Libretro나 단순 예제까지 모두 같은 스레드 구성을 쓰는 것은 아닙니다.
Qt 경로에서는 CoreController가 코어 스레드를 관리하고,
src/core/thread.c의 _mCoreThreadRun()이 실행 상태에서 core->runLoop(core)를 반복합니다.
include/mgba/core/core.h의 struct mCore에는 init, runFrame,
step, busRead32 같은 함수 포인터가 있습니다.
호출자는 같은 이름으로 요청하지만, 실제 실행되는 함수는 코어에 따라 달라집니다.
// src/gba/core.c: GBACoreCreate()의 함수표 연결 중 일부
core->init = _GBACoreInit;
core->reset = _GBACoreReset;
core->runFrame = _GBACoreRunFrame;
core->runLoop = _GBACoreRunLoop;
core->step = _GBACoreStep;
src/core/core.c의 _filters[]는 GBAIsROM과 GBACoreCreate,
GBIsROM과 GBCoreCreate를 짝짓습니다.
mCoreFindVF()는 판별 함수가 맞는 항목의 생성 함수를 호출합니다.
팩터리는 어떤 구현을 만들지 결정하고, 다형성은 만들어진 구현을 같은 방식으로 사용하는 성질입니다.
리모컨의 재생 버튼은 같지만 TV와 오디오 내부의 동작은 다릅니다. 프론트엔드는 리모컨을 사용하고, 코어 생성 함수는 알맞은 기기의 버튼 배선을 연결합니다.
배울 점: C++의 상속 문법이 없어도 역할을 분리할 수 있습니다.
대신 함수표 초기화, 객체 수명, 포인터 형식의 일관성을 개발자가 책임져야 합니다.
_GBACoreInit()과 _GBACoreDeinit()를 짝지어 읽으면 자원 소유권도 드러납니다.
src/arm/arm.c의 ARMStep()는 opcode의 비트를 뽑아 _armTable에서 실행 함수를 고릅니다.
ThumbStep()는 _thumbTable[opcode >> 6]을 사용합니다.
명령어 실행으로 사이클이 누적되고, ARMRunLoop()는 cpu->cycles < cpu->nextEvent인 동안 진행합니다.
이벤트 시각에 닿으면 GBAProcessEvents()를 거쳐 mTimingTick()으로 넘어갑니다.
mTimingSchedule()은 이벤트를 시각순 연결 리스트에 넣습니다.
시각이 같으면 priority 값이 작은 항목이 앞섭니다. 힙이나 OS 타이머를 쓰는 구현은 아닙니다.
# 스케줄러 규칙을 손으로 따라가는 예: 값은 학습용
현재 시각: 100
등록 순서: A(시각 130, 우선순위 8)
B(시각 120, 우선순위 9)
C(시각 130, 우선순위 3)
정렬 결과: B -> C -> A
시각 132에 처리: B는 12, C와 A는 2 사이클 늦음
mTimingTick()은 이미 지난 이벤트를 꺼내 콜백에 지연량을 넘깁니다.
영상 콜백 _startHblank()와 _startHdraw()는 다음 예약에서 cyclesLate를 뺍니다.
늦게 처리된 만큼 매번 다음 예약까지 밀리면 오차가 계속 쌓이므로 이를 보정하는 것입니다.
가상 시간을 정확히 계산하는 일과 실제 화면·소리의 재생 속도를 맞추는 일은 서로 다른 문제입니다. 이 구분이 있어야 빠른 감기, 일시정지, 디버깅을 넣어도 하드웨어의 내부 시간 관계를 설명할 수 있습니다.
include/mgba-util/vfs.h의 VFile은 read, seek, map,
close 등의 함수 포인터를 묶습니다.
디스크 파일, 메모리 블록, 압축 파일 내부 항목을 코어가 비슷한 방식으로 다루도록 하는 계약입니다.
// src/core/test/core.c의 findEmpty 테스트를 간추린 흐름
struct VFile* vf = VFileMemChunk(NULL, 0);
struct mCore* core = mCoreFindVF(vf);
// 빈 데이터이므로 ROM 판별 결과는 NULL이어야 합니다.
vf->close(vf);
src/core/core.c의 mCoreFind()는 압축 디렉터리를 열 수 있으면 내부 항목을 순회해 코어를 찾습니다.
mCoreLoadFile()은 로딩이 실패하면 열린 ROM을 닫습니다.
단순히 파일 경로만 추상화한 것이 아니라, 열린 자원의 처리 책임까지 함께 읽어야 합니다.
배울 점: 파일을 메모리로 대체할 수 있으면 작은 테스트가 쉬워집니다. 네트워크·스토리지 코드를 배울 때도 실제 외부 서비스 없이 경계 조건을 확인하는 설계로 연결할 수 있습니다.
코어는 오디오 샘플을 생산하고, 실제 오디오 출력 쪽은 이를 소비합니다.
src/util/audio-buffer.c는 mCircleBuffer 위에 샘플 버퍼를 만듭니다.
mAudioBufferAvailable()은 바이트 수를 채널 수와 sizeof(int16_t)로 나누어 샘플 프레임 수로 바꿉니다.
src/core/sync.c의 mCoreSyncProduceAudio()는 동기화가 켜져 있고 버퍼가 상한 이상이면 조건 변수로 기다립니다.
mCoreSyncConsumeAudio()는 소비 후 대기 중인 쪽을 깨웁니다.
초기 audioHighWater는 512이지만, 이것을 모든 환경의 지연 시간으로 환산해서는 안 됩니다.
// CoreController::updateFastForward()의 정상 속도 복귀 분기 일부
if (isChange) {
mCoreSyncLockAudio(&m_threadContext.impl->sync);
mAudioBufferClear(
m_threadContext.core->getAudioBuffer(m_threadContext.core));
mCoreSyncUnlockAudio(&m_threadContext.impl->sync);
}
커밋 fd9dc5c의 메시지는 빠른 감기 종료 시 쌓인 오디오로 잠깐 멈출 수 있던 문제를 설명합니다.
실제 코드도 src/platform/qt/CoreController.cpp에서 정상 속도 복귀 시 버퍼를 잠그고 비웁니다.
커밋 설명의 최대 약 500ms는 해당 문제의 맥락이며, 일반 사용 시 항상 그만큼 지연된다는 뜻은 아닙니다.
배울 점: 처리량이 높아도 오래된 결과가 큐에 남으면 반응성은 나빠집니다. 버퍼 용량만 늘리는 대신, 속도 전환 때 어떤 데이터를 버려도 되는지 결정하는 일이 필요합니다.
| 단계 | 파일·함수 | 확인할 상태 변화 |
|---|---|---|
| 명령 해석 | src/arm/arm.c · ARMStep() | prefetch, 프로그램 카운터, 조건 플래그, 실행 함수 선택 |
| 메모리 접근 | src/gba/memory.c · GBALoad32() | 주소 상위 비트로 영역 선택, 대기 사이클 반영, 비정렬 읽기 회전 |
| 이벤트 처리 | src/gba/gba.c · GBAProcessEvents() | 명령 실행에서 쌓인 사이클을 스케줄러에 전달 |
| 한 줄 그리기 | src/gba/video.c · _startHblank() | 표시 구간의 줄이며 프레임 건너뛰기 대상이 아닐 때 렌더러의 drawScanline 호출 |
| 프레임 전달 | 같은 파일 · _startHdraw() | 수직 공백 시작에서 프레임 완료·DMA·인터럽트·동기화 처리 |
메모리는 하나의 거대한 배열로만 취급되지 않습니다.
include/mgba/internal/gba/memory.h는 0x02000000을 EWRAM,
0x04000000을 I/O, 0x06000000을 VRAM, 0x08000000을 ROM 시작 주소로 정의합니다.
같은 읽기 명령도 주소에 따라 다른 하드웨어 규칙을 거칩니다.
영상 시각을 관리하는 코드와 픽셀을 만드는 렌더러도 나뉩니다.
GBAVideoAssociateRenderer()가 렌더러를 연결하고,
_GBACoreInit()는 더미·소프트웨어 렌더러와 조건부 GL 렌더러를 준비합니다.
화면 출력의 구현을 바꾸는 일과 CPU 명령을 바꾸는 일을 분리한 구조입니다.
src/gba/serialize.c의 GBASerialize()는 버전·체크섬뿐 아니라 CPU 레지스터,
prefetch, 현재 사이클 등도 기록합니다.
GBADeserialize()는 버전, 게임 정보, 사이클 값 등의 일관성을 검사합니다.
화면이 같아도 다음 명령이나 예약된 이벤트가 다르면 이후 결과가 달라집니다. 영상 코드가 콜백 전에 상태를 일관되게 맞추라고 주석으로 강조하는 이유도 여기에 있습니다. 저장할 상태를 정의하는 일은 시스템의 실제 상태가 무엇인지 정의하는 일이기도 합니다.
주요 근거: 이벤트 스케줄러 · ARM 실행 루프 · Qt 코어 제어.
| 경계 | 역할의 차이 | 처음 던질 질문 |
|---|---|---|
| core ↔ gba·gb | 공통 호출 규약과 기종별 구현 | 프론트엔드가 CPU 종류를 직접 알아야 하는가? |
| arm ↔ gba | 명령어 의미와 GBA 보드의 장치 구성 | CPU가 메모리 읽기 함수를 어떻게 받는가? |
| 영상 타이밍 ↔ 렌더러 | 그릴 시점과 픽셀 생성 방식 | 프레임 건너뛰기에도 어떤 이벤트는 계속 필요한가? |
| util ↔ platform | 재사용 도구와 OS·라이브러리별 연결 | 다른 기기에 이식할 때 무엇을 바꿔야 하는가? |
| test ↔ cinema | 검사 도구와 검사에 쓰는 데이터 | 실행 파일의 종료 코드만으로 그래픽 오류를 찾을 수 있는가? |
| 분류 | 파일 수 | 물리적 줄 수 |
|---|---|---|
| C · .c | 251 | 111,978 |
| 헤더 · .h | 321 | 46,736 |
| C++ · .cpp·.cc | 118 | 30,991 |
| Python · .py | 32 | 2,925 |
| Lua · .lua | 12 | 1,147 |
| 어셈블리 · .s·.S | 1 | 400 |
커밋 fd9dc5c의 해당 확장자 파일을 줄 단위로 집계한 값입니다.
.git/과 src/third-party/는 제외하고 주석·빈 줄·테스트는 포함했습니다.
헤더는 C/C++가 공유하므로 별도 분류했습니다. 전체 파일 수나 실행 문장 수를 뜻하지 않습니다.
제외한 서드파티의 C만 따로 세면 397,425줄입니다. 이를 코어 구현량에 합치면 외부 라이브러리가 프로젝트의 자체 코드보다 크게 보일 수 있습니다. 규모를 비교할 때는 집계 범위를 먼저 맞추는 습관이 필요합니다.
src/platform/example/client-server/server.c에서 호출 흐름을 잡고,
include/mgba/core/core.h → src/gba/core.c → src/arm/arm.c →
src/core/timing.c → src/gba/video.c로 좁혀 가세요.
예제의 전체 경로는 src/platform/example/client-server/server.c입니다.
struct mCore를 읽을 때는 함수 이름보다 인자와 반환형을 먼저 보세요.
void (*step)(struct mCore*)는 코어 상태를 받아 한 단계를 진행하는 계약입니다.
bool (*loadROM)(struct mCore*, struct VFile*)에는 실패 가능성과 열린 파일의 수명 문제가 함께 붙습니다.
명령어·레지스터·주소에는 uint32_t, 샘플에는 int16_t 같은 형식이 등장합니다.
데이터 폭이 곧 하드웨어 규칙이기 때문입니다.
포인터 주소 자체를 저장 파일에 쓰는 방식이 아니라, 복원할 값과 형식을 명시하는 방식에 주목하세요.
init, runLoop, deinit에 대해 선언·대입·실제 구현 위치를 한 줄씩 적어 보세요.
각 단계가 만드는 자원과 해제하는 자원을 옆에 붙이면 함수 포인터와 메모리 수명을 함께 익힙니다.
GBALoad32()는 address >> BASE_OFFSET으로 영역을 고릅니다.
BASE_OFFSET은 24입니다.
주소의 상위 8비트가 어떤 장치를 가리키고, 아래 비트가 그 안의 위치를 뜻하는지 먼저 구분하세요.
# 주소 영역을 읽는 연습: 실제 장치 접근은 하지 않습니다.
0x02000000 >> 24 = 0x02 EWRAM
0x04000130 >> 24 = 0x04 I/O 영역
0x06000000 >> 24 = 0x06 VRAM
0x08000000 >> 24 = 0x08 ROM 영역
다음으로 비정렬 접근을 보세요. GBALoad32()의 마지막에는 주소 하위 비트로 회전량을 계산해
ROR(value, rotate)를 반환하는 코드가 있습니다.
호스트 언어의 자연스러운 배열 접근과 가상 CPU의 동작 규칙이 다를 수 있음을 보여 줍니다.
mTimingEvent 안에 next 포인터가 있어 이벤트 자신이 연결 리스트의 노드가 됩니다.
mTimingSchedule()은 삽입 지점을 순회하므로 이벤트 수에 따라 비용이 늘어납니다.
매번 전용 노드를 새로 할당하지 않는 구성도 함께 읽을 수 있습니다.
이것이 모든 상황의 최적 선택이라는 뜻은 아닙니다. 하드웨어 이벤트 수가 작고 정해진 환경과, 수십만 작업이 등록되는 서버 환경은 다릅니다. 같은 규칙을 배열·최소 힙으로 표현하면 어떤 연산이 쉬워지는지 비교하는 것이 좋은 실습입니다.
mCoreSyncProduceAudio()의 대기 조건을 읽고, 버퍼 값의 단위부터 적어 보세요.
바이트 수, 채널별 값의 수, 샘플 프레임 수는 서로 다릅니다.
mAudioResamplerProcess()에서는 sourceRate / destRate가 보간 위치의 증가량으로 쓰입니다.
손계산 연습: 가정한 출력이 48,000Hz·스테레오·16비트라면 100ms 분량은
48,000 × 0.1 × 2 × 2 = 19,200바이트입니다.
이는 단위 변환 예시이며, mGBA의 기본 출력 설정이나 측정 지연을 뜻하지 않습니다.
Qt의 CoreController는 UI에서 발생한 요청을 코어 실행·설정과 연결합니다.
빠른 감기 처리를 읽은 뒤에는 같은 클래스의 setFastForward(), setSync()를 찾아
버튼 상태와 실제 실행 설정이 어떻게 연결되는지 추적해 보세요.
Lua 쪽은 src/core/scripting.c가 코어 메서드를 공개 이름에 연결합니다.
예를 들어 C의 frameCounter는 스크립트에 currentFrame으로 노출됩니다.
res/scripts/input-display.lua는 callbacks:add("frame", ...) 등록을 보여 줍니다.
-- 개발 버전의 바인딩을 바탕으로 만든 학습용 예제
local frames = 0
callbacks:add("frame", function()
frames = frames + 1
if frames % 60 == 0 then
console:log("프레임: " .. emu:currentFrame())
end
end)
60프레임마다 출력하는 예제이지, 정확히 1초마다 출력하는 시계는 아닙니다. 빠른 감기나 일시정지에서는 실제 경과 시간이 달라집니다. Lua 공식 매뉴얼은 언어 학습용이며, mGBA 내장 Lua 버전을 5.4로 고정한다는 뜻은 아닙니다.
Python은 src/platform/python/setup.py의 cffi_modules와
src/platform/python/_builder.py를 이어서 읽으세요.
패키지 설치 명령보다 먼저, C 함수와 Python 객체 사이에 어떤 형식·수명 변환이 필요한지 정리하면 좋습니다.
| 수준 | 실제 예 | 알 수 있는 것 |
|---|---|---|
| 자료구조 단위 | src/util/test/circle-buffer.c | 값·정렬·용량 등 버퍼의 개별 규칙 |
| 코어 경계 | src/core/test/core.c | NULL·빈 파일 같은 입력에서의 판별 동작 |
| 화면 회귀 | cinema/, src/platform/test/cinema-main.c | 정해진 조건에서 기준 화면과 결과가 달라지는지 |
src/platform/test/cinema-main.c의 _compareImages()는 화면 차이를 비교합니다.
테스트 준비에서는 RTC_FAKE_EPOCH로 RTC의 기준 시각을 고정하고, 프레임 진행에 따라 시간을 계산해 실제 시계에 따른 변동을 줄입니다.
좋은 테스트를 설계하려면 결과뿐 아니라 결과를 만드는 입력·시간·상태도 고정해야 합니다.
| 항목 | 소스 기준 값 | 읽는 위치 |
|---|---|---|
| CPU 기준 주파수 | 16,777,216Hz | include/mgba/internal/gba/gba.h: #define GBA_ARM7TDMI_FREQUENCY 0x1000000U |
| 기본 화면 | 240 × 160픽셀 | include/mgba/gba/interface.h의 화면 상수 |
| 한 줄·한 프레임 | 1,232사이클 · 228줄 · 280,896사이클 | include/mgba/internal/gba/video.h |
| 메모리 일부 | EWRAM 256KiB · IWRAM 32KiB · VRAM 96KiB | include/mgba/internal/gba/memory.h의 영역 크기 |
# 소스 상수로 계산한 가상 프레임 속도
1,232 × 228 = 280,896 사이클 / 프레임
16,777,216 ÷ 280,896 ≈ 59.7275 프레임 / 초
# 32비트 픽셀 버퍼 하나를 가정한 크기
240 × 160 × 4 = 153,600 바이트 = 150 KiB
픽셀 버퍼 계산은 32비트 출력의 예시이며 프로세스 전체 메모리 사용량이 아닙니다. GUI, ROM, 렌더러, 오디오 버퍼, 되감기 상태가 추가 공간을 차지합니다. 가상 CPU가 16.78MHz라고 해서 같은 주파수의 호스트 CPU면 충분하다는 뜻은 아닙니다.
| 항목 | 안내·조건 | 판단 기준 |
|---|---|---|
| 데스크톱 OS | README: Windows 7 이상, OS X 10.9 이상, Linux, FreeBSD | 개별 배포물과 Qt·SDL 버전에 따라 실제 요구가 높아질 수 있음 |
| 그래픽 | README: OpenGL 1.1 이상, 셰이더 등 고급 기능은 3.2 이상 | 선택한 출력 경로와 드라이버 지원을 확인 |
| CPU·RAM | 고정 CPU 모델·최소 RAM 용량은 README에 명시되지 않음 | 필터·녹화·되감기·빠른 감기 조건에 따라 자원 요구가 달라짐 |
| 게임 입력 | 키보드, 게임패드. 일부 센서는 대응 장치·설정 필요 | README의 기능 목록과 해당 프론트엔드 지원을 구분 |
| BIOS | 내장 구현과 외부 BIOS 로드 지원 | 외부 BIOS가 없다는 이유만으로 모든 실행이 불가능한 구조는 아님 |
README의 시스템 요구 설명에는 Vista급 컴퓨터라는 표현도 있으나, 지원 플랫폼 목록의 Windows 하한은 7입니다. 하드웨어 성능에 대한 대략적인 설명과 현재 지원 OS 목록을 같은 의미로 읽으면 안 됩니다. macOS도 Qt CMake 분기에서 Qt 5.15는 10.13, Qt 6은 SDK 조건에 따라 10.14·10.15를 설정합니다.
코어 학습에는 C11 컴파일러와 CMake 3.12 이상이 기준이며, Qt 프론트엔드는 C++14 환경도 필요합니다. Python 바인딩은 기본 빌드 대상이 아닙니다. 테스트·녹화·스크립트를 다룰 때 cmocka·FFmpeg·Lua 등의 선택 의존성이 추가됩니다.
첫 학습에서는 녹화·필터·되감기를 한꺼번에 켜기보다 기능별 자원 비용을 구분하세요. 이 자료의 수치는 소스의 상수와 구조에 근거하며, 특정 기기에서의 프레임 속도나 빌드 성공을 보장하지 않습니다.
각 과제에는 남겨야 할 결과물을 정했습니다. 먼저 정적 추적과 손계산을 끝낸 뒤, 실행 환경이 준비된 경우 선택 실습으로 넓혀 보세요. 함수 이름을 찾는 데서 멈추지 말고, 입력·상태 변화·예상 결과를 한 묶음으로 설명해 보세요.
목표: 함수 포인터를 실제 동작에 연결합니다.
include/mgba/core/core.h에서 init·step·runFrame을 찾습니다.GBACoreCreate()에서 각 항목에 연결하는 함수를 찾습니다._GBACoreStep()과 _GBACoreRunFrame()의 반복 조건을 비교합니다.완료 기준: 선언 → 연결 → 실행의 세 열 표와, 한 명령 실행·한 프레임 실행의 차이를 설명한 문단을 만듭니다. 프레임 함수에 시간 상한 조건도 있다는 점을 포함하세요.
목표: 메모리 매핑과 단위 변환을 익힙니다.
0x02000000, 0x04000000, 0x06000000, 0x08000000의 영역 번호를 계산합니다.include/mgba/internal/gba/memory.h의 영역 상수와 GBALoad32()의 분기를 연결합니다.완료 기준: 주소·영역·역할·크기를 담은 표를 작성합니다. I/O 주소가 일반 RAM과 같은 읽기 규칙을 갖는다고 가정하지 마세요.
목표: 시각·우선순위·지연 보정을 함께 이해합니다.
mTimingSchedule()의 조건대로 삽입합니다.완료 기준: A가 시각 130에 예정되어 있었다면 다음 목표는 150입니다.
132에서 단순히 20을 더한 152와, 지연량 2를 뺀 150의 차이를 설명합니다.
값이 작은 priority가 먼저라는 규칙도 확인하세요.
목표: C 메서드가 스크립트 API가 되는 연결을 읽습니다.
src/core/scripting.c에서 currentFrame·getKeys의 연결을 찾습니다.res/scripts/input-display.lua에서 프레임 콜백 등록과 입력 조회 위치를 찾습니다.선택 실행: Lua 기능이 포함된 빌드와 사용 권한이 있는 테스트 ROM이 준비되면 6절 예제를 확장할 수 있습니다. 완료 기준: API 대응표와, 프레임 수를 실제 초로 간주할 수 없는 이유를 남깁니다.
목표: 증상을 큐 상태와 동기화 조건으로 표현합니다.
updateFastForward()에서 정상 복귀·버퍼 초기화·잠금의 순서를 적습니다.src/core/sync.c에서 연결합니다.완료 기준: 최소 네 사례의 사전 상태·조작·예상 상태 표를 만듭니다. 버퍼만 따로 비우는 단위 검사와 실제 출력 스레드를 포함한 통합 검사를 구분하세요.
고급 확장으로는 cinema/gb/acid/config.ini의 skip=15, frames=1을 읽고,
src/platform/test/cinema-main.c가 어떤 프레임부터 기준 화면과 비교하는지 추적할 수 있습니다.
기준 화면을 갱신하는 일과 구현 오류를 고치는 일을 구분하는 것이 핵심입니다.
C의 변수·반복문·함수·기본 포인터를 알고 있다는 전제입니다. 아직 포인터가 낯설다면 1주 차를 두 번 반복하세요. 매주 소스에서 읽은 규칙, 손으로 계산한 예, 자신의 말로 쓴 설명을 하나씩 남깁니다.
| 주차 | 주제·읽을 파일 | 남길 결과물 |
|---|---|---|
| 1주 차 | C 구조체·함수 포인터·객체 수명include/mgba/core/core.h, src/gba/core.c |
함수표 5개 항목의 선언·구현 대응표. 초기화와 해제 자원 목록 |
| 2주 차 | CPU·명령어·주소 공간src/arm/arm.c, src/gba/memory.c |
한 명령의 인출·디코딩·실행·메모리 접근 지도. 주소 해석 예시 |
| 3주 차 | 사이클·이산 이벤트·영상 타이밍src/core/timing.c, src/gba/video.c |
시각·우선순위가 다른 이벤트 5개를 손으로 처리한 표 |
| 4주 차 | 스레드·조건 변수·오디오 큐src/core/sync.c, src/util/audio-buffer.c |
버퍼 단위 변환표와 생산·소비·대기·깨우기 순서도 |
| 5주 차 | 직렬화·복원·되감기src/gba/serialize.c, src/core/rewind.c |
다음 실행 결과에 영향을 주는 상태 목록. 복원 시 검증할 조건 |
| 6주 차 | 프론트엔드·스크립트·회귀 검사src/core/scripting.c, src/platform/test/cinema-main.c |
기능 하나의 코어→화면 연결 설명과 회귀 검사 계획 |
src/gba/renderers/에서 타일·스프라이트·레이어가 픽셀로 합쳐지는 경로를 읽습니다.src/util/audio-resampler.c와 보간기를 읽고, 샘플률 변환과 버퍼 지연을 분리해 공부합니다.전체 코드를 다 읽기보다 기능 하나가 여러 계층을 통과하는 경로를 완주하는 편이 설계를 이해하는 데 도움이 됩니다. 예를 들어 되감기를 골랐다면 상태 저장, 차이 계산, 큐 용량, 복원 시점까지 연결해 보세요.
도시의 모든 도로 이름을 외우기 전에, 집에서 학교까지 한 번 걸어 보세요. 한 경로의 교차로를 이해한 뒤 다른 길로 확장하면 지도가 실제 동작으로 읽힙니다.
| 용어 | 쉬운 설명 |
|---|---|
| 에뮬레이터 | 다른 기계의 명령과 장치 규칙을 소프트웨어로 재현하는 프로그램입니다. |
| 코어 / 프론트엔드 | 코어는 기계의 동작을 계산하고, 프론트엔드는 창·입력·출력을 사용자에게 연결합니다. |
| ROM | 이 문서에서는 게임 카트리지의 프로그램과 데이터를 담은 파일을 가리킵니다. |
| BIOS | 기기의 시작과 기본 기능을 담당하는 펌웨어입니다. mGBA에는 내장 동작 구현도 있습니다. |
| ARM / Thumb | GBA CPU가 사용하는 명령 체계입니다. 여기서 ARM 명령은 32비트, Thumb 명령은 16비트 폭입니다. |
| opcode | CPU가 실행할 동작을 비트로 표현한 명령 값입니다. 디코더가 비트를 해석해 실행 함수를 고릅니다. |
| 레지스터 | CPU 안의 작은 작업 공간입니다. PC는 다음 실행 흐름과 관련된 위치를 나타내는 레지스터입니다. |
| prefetch | 앞으로 사용할 명령을 미리 가져온 상태입니다. 정확한 복원에는 이런 내부 상태도 중요합니다. |
| 사이클 | 가상 하드웨어의 진행을 세는 시간 단위입니다. 한 명령이 항상 한 사이클이라는 뜻은 아닙니다. |
| 메모리 매핑 | 주소 범위를 RAM·화면 메모리·장치 레지스터 등에 배정하는 규칙입니다. |
| I/O 레지스터 | 특정 주소에서 읽고 쓰는 장치 제어 값입니다. 일반 메모리와 다른 부수 효과가 있을 수 있습니다. |
| VRAM / OAM | 화면용 데이터 공간과 스프라이트 속성 공간입니다. 영상 구현에서 각각 다른 역할을 맡습니다. |
| DMA | CPU가 데이터를 일일이 옮기는 대신 전송 장치가 메모리 사이의 이동을 수행하는 기능입니다. |
| 인터럽트 / IRQ | 장치가 CPU에 사건을 알리는 신호입니다. 조건에 따라 CPU가 처리 흐름을 바꿉니다. |
| HBlank / VBlank | 한 줄·한 화면의 영상 진행에서 표시 구간 밖의 시기입니다. DMA나 인터럽트와도 연결됩니다. |
| 렌더러 | 영상 상태를 실제 픽셀로 바꾸는 부품입니다. 그릴 시점을 정하는 코드와 구분해 읽습니다. |
| 함수 포인터 | 호출할 함수의 위치를 담는 값입니다. mCore는 이를 모아 여러 기종을 같은 방식으로 다룹니다. |
| 팩터리 | 조건에 맞는 구현 객체를 만들어 주는 생성 경계입니다. ROM 판별 뒤 코어 생성이 그 예입니다. |
| 이산 이벤트 | 다음 사건의 시각을 예약하고 그 순서대로 상태를 바꾸는 계산 방식입니다. |
| VFS / VFile | 파일의 실제 출처를 감춘 공통 읽기·쓰기 계약입니다. 테스트에서는 메모리 파일로 대체할 수 있습니다. |
| 뮤텍스 / 조건 변수 | 공유 상태 접근을 조절하는 잠금과, 조건이 바뀔 때까지 기다렸다가 깨우는 동기화 도구입니다. |
| 순환 버퍼 | 고정 공간의 끝에 닿으면 처음으로 돌아가며 데이터를 넣고 빼는 큐입니다. |
| 샘플 프레임 | 같은 시점의 모든 채널 값을 묶은 오디오 단위입니다. 스테레오 16비트라면 한 묶음이 4바이트입니다. |
| 리샘플링 | 서로 다른 샘플률 사이에서 오디오 값을 새 시간 간격에 맞춰 만드는 처리입니다. |
| 직렬화 / savestate | 기계 상태를 저장 가능한 형식으로 표현하는 처리와 그 결과입니다. 게임 내부 저장 기능과 범위가 다릅니다. |
| 엔디언 | 여러 바이트짜리 수를 어떤 순서로 저장하는지에 관한 규칙입니다. 파일 형식과 CPU 재현에서 중요합니다. |
| MBC | Game Boy 카트리지에서 보이는 메모리 뱅크를 바꾸는 제어 장치입니다. |
| FFI / CFFI | 다른 언어의 함수를 호출하는 연결 방식과, 이 저장소의 Python 연결에 쓰는 도구입니다. |
| 회귀 검사 / 기준 화면 | 변경 후 예전 동작이 깨졌는지 확인하는 검사와, 영상 결과를 비교할 기준 이미지입니다. |
| ABI | 컴파일된 구성 요소가 함수 호출과 데이터 배치 등에서 맞춰야 하는 바이너리 수준의 계약입니다. |
| 헤드리스 | 일반적인 GUI 창 없이 코어를 구동하는 형태입니다. 자동화와 테스트 도구에서 유용합니다. |
용어를 기억하기 어렵다면 실제 식별자 하나와 묶으세요.
다형성은 mCore, 이벤트는 mTimingEvent, 파일 추상화는 VFile,
역압은 mCoreSyncProduceAudio()를 떠올리면 됩니다.
| 항목 | 값·범위 |
|---|---|
| 저장소 | mgba-emu/mgba · 설명: mGBA Game Boy Advance Emulator |
| 선정 기준 | TrendShift Repositories #2 · 2026-10-23 분석 |
| 스타 / 포크 | 7,521 / 1,139 · 2026-10-23 GitHub 기준 |
| 주 언어 / 생성일 | C / 2014-12-09 · 2026-10-23 GitHub 기준 |
| 최신 정식 릴리스 | 0.10.5 · 2025-03-09 · 2026-10-23 GitHub 기준 |
| 분석 소스의 버전 | version.cmake: 0.11.0, ABI 0.11 · CHANGES는 Future로 표시 |
| 최근 push | 2026-10-19 · 2026-10-23 GitHub 기준. 커밋 작성일과는 다른 지표 |
| 분석 기준 커밋 | fd9dc5c · Util: Flush audio buffer on fast-forward end |
| 커밋 날짜의 구분 | 작성일 2026-09-25 / 반영일 2026-10-19 UTC · Git 기록 기준 |
| 자체 코드 규모 | C 111,978줄 + 헤더 46,736줄 + C++ 30,991줄. Python 2,925줄, Lua 1,147줄, 어셈블리 400줄 |
| 집계 범위 | 물리적 줄 수, 주석·빈 줄·테스트 포함. .git/·src/third-party/ 제외. 확장자 기준 |
| 라이선스 | MPL-2.0 · 저장소 LICENSE. 동봉 라이브러리의 고지는 별도 확인 |
이 글의 구현 설명은 커밋 fd9dc5c에 고정되어 있습니다.
0.11.0 개발 항목을 0.10.5의 기능으로 간주하지 마세요.
성능·게임별 정확도·설치 성공 여부는 운영체제, 라이브러리, ROM과 설정별 검증이 필요합니다.
README의 정확도·저사양 실행 표현은 프로젝트의 안내이며, 경쟁 도구와 같은 조건으로 측정한 결론은 아닙니다. 통신의 로컬 지원과 네트워크 지원 계획도 다릅니다. 코드 규모는 특정 커밋의 파일 집계이며, 전체 커밋 수나 개발 속도·기여자 비중을 나타내지 않습니다.
CONTRIBUTING.md는 AI가 생성한 코드의 PR을 받지 않는다고 명시합니다.
실습 결과를 기여로 발전시키려면 해당 지침을 먼저 읽으세요.
게임·BIOS 파일의 배포 권한은 에뮬레이터 소스 라이선스와 별개의 문제입니다.