all repos — mgba @ c39162732d966e379c68a11fed6371276c3f9eb5

mGBA Game Boy Advance Emulator

src/platform/wii/main.c (view raw)

  1/* Copyright (c) 2013-2015 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#define asm __asm__
  7
  8#include <fat.h>
  9#include <gccore.h>
 10#include <malloc.h>
 11
 12#include "util/common.h"
 13
 14#include "gba/gba.h"
 15#include "gba/renderers/video-software.h"
 16#include "gba/serialize.h"
 17#include "gba/supervisor/overrides.h"
 18#include "gba/video.h"
 19#include "util/gui.h"
 20#include "util/gui/file-select.h"
 21#include "util/gui/font.h"
 22#include "util/vfs.h"
 23
 24#define SAMPLES 1024
 25
 26static void GBAWiiLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args);
 27static void GBAWiiFrame(void);
 28static bool GBAWiiLoadGame(const char* path);
 29
 30static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
 31static void _audioDMA(void);
 32
 33static void _drawStart(void);
 34static void _drawEnd(void);
 35static int _pollInput(void);
 36
 37static struct GBA gba;
 38static struct ARMCore cpu;
 39static struct GBAVideoSoftwareRenderer renderer;
 40static struct VFile* rom;
 41static struct VFile* save;
 42static struct GBAAVStream stream;
 43static FILE* logfile;
 44static GXRModeObj* mode;
 45static Mtx model, view, modelview;
 46static uint16_t* texmem;
 47static GXTexObj tex;
 48
 49static void* framebuffer[2];
 50static int whichFb = 0;
 51
 52static struct GBAStereoSample audioBuffer[3][SAMPLES] __attribute__((__aligned__(32)));
 53static volatile size_t audioBufferSize = 0;
 54static volatile int currentAudioBuffer = 0;
 55
 56static struct GUIFont* font;
 57
 58int main() {
 59	VIDEO_Init();
 60	PAD_Init();
 61	AUDIO_Init(0);
 62	AUDIO_SetDSPSampleRate(AI_SAMPLERATE_48KHZ);
 63	AUDIO_RegisterDMACallback(_audioDMA);
 64
 65	memset(audioBuffer, 0, sizeof(audioBuffer));
 66
 67#if !defined(COLOR_16_BIT) && !defined(COLOR_5_6_5)
 68#error This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
 69#endif
 70
 71	mode = VIDEO_GetPreferredMode(0);
 72	framebuffer[0] = SYS_AllocateFramebuffer(mode);
 73	framebuffer[1] = SYS_AllocateFramebuffer(mode);
 74
 75	VIDEO_Configure(mode);
 76	VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
 77	VIDEO_SetBlack(FALSE);
 78	VIDEO_Flush();
 79	VIDEO_WaitVSync();
 80	if (mode->viTVMode & VI_NON_INTERLACE) {
 81		VIDEO_WaitVSync();
 82	}
 83
 84	GXColor bg = { 0, 0, 0, 0xFF };
 85	void* fifo = memalign(32, 0x40000);
 86	memset(fifo, 0, 0x40000);
 87	GX_Init(fifo, 0x40000);
 88	GX_SetCopyClear(bg, 0x00FFFFFF);
 89	GX_SetViewport(0, 0, mode->fbWidth, mode->efbHeight, 0, 1);
 90
 91	f32 yscale = GX_GetYScaleFactor(mode->efbHeight, mode->xfbHeight);
 92	u32 xfbHeight = GX_SetDispCopyYScale(yscale);
 93	GX_SetScissor(0, 0, mode->viWidth, mode->viWidth);
 94	GX_SetDispCopySrc(0, 0, mode->fbWidth, mode->efbHeight);
 95	GX_SetDispCopyDst(mode->fbWidth, xfbHeight);
 96	GX_SetCopyFilter(mode->aa, mode->sample_pattern, GX_TRUE, mode->vfilter);
 97	GX_SetFieldMode(mode->field_rendering, ((mode->viHeight == 2 * mode->xfbHeight) ? GX_ENABLE : GX_DISABLE));
 98
 99	GX_SetCullMode(GX_CULL_NONE);
100	GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
101	GX_SetDispCopyGamma(GX_GM_1_0);
102
103	GX_ClearVtxDesc();
104	GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
105	GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
106
107	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0);
108	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
109
110	GX_SetNumChans(1);
111	GX_SetNumTexGens(1);
112	GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
113	GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE);
114
115	GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
116	GX_InvVtxCache();
117	GX_InvalidateTexAll();
118
119	Mtx44 proj;
120
121	guVector cam = { 0.0f, 0.0f, 0.0f };
122	guVector up = { 0.0f, 1.0f, 0.0f };
123	guVector look = { 0.0f, 0.0f, -1.0f };
124	guLookAt(view, &cam, &up, &look);
125
126	guMtxIdentity(model);
127	guMtxTransApply(model, model, 0.0f, 0.0f, -6.0f);
128	guMtxConcat(view, model, modelview);
129	GX_LoadPosMtxImm(modelview, GX_PNMTX0);
130
131	texmem = memalign(32, 256 * 256 * BYTES_PER_PIXEL);
132	memset(texmem, 0, 256 * 256 * BYTES_PER_PIXEL);
133	GX_InitTexObj(&tex, texmem, 256, 256, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE);
134
135	font = GUIFontCreate();
136
137	fatInitDefault();
138
139	logfile = fopen("/mgba.log", "w");
140
141	stream.postAudioFrame = 0;
142	stream.postAudioBuffer = 0;
143	stream.postVideoFrame = _postVideoFrame;
144
145	GBACreate(&gba);
146	ARMSetComponents(&cpu, &gba.d, 0, 0);
147	ARMInit(&cpu);
148	gba.logLevel = 0; // TODO: Settings
149	gba.logHandler = GBAWiiLog;
150	gba.stream = &stream;
151	gba.idleOptimization = IDLE_LOOP_REMOVE; // TODO: Settings
152	rom = 0;
153
154	GBAVideoSoftwareRendererCreate(&renderer);
155	renderer.outputBuffer = memalign(32, 256 * 256 * BYTES_PER_PIXEL);
156	renderer.outputBufferStride = 256;
157	GBAVideoAssociateRenderer(&gba.video, &renderer.d);
158
159	GBAAudioResizeBuffer(&gba.audio, SAMPLES);
160
161#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
162	blip_set_rates(gba.audio.left,  GBA_ARM7TDMI_FREQUENCY, 48000);
163	blip_set_rates(gba.audio.right, GBA_ARM7TDMI_FREQUENCY, 48000);
164#endif
165
166	char path[256];
167	guOrtho(proj, -20, 220, 0, 400, 0, 300);
168	GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
169
170	struct GUIParams params = {
171		400, 230,
172		font, _drawStart, _drawEnd, _pollInput
173	};
174	if (!selectFile(&params, "/", path, sizeof(path), "gba") || !GBAWiiLoadGame(path)) {
175		free(renderer.outputBuffer);
176		GUIFontDestroy(font);
177		return 1;
178	}
179
180	guOrtho(proj, 0, VIDEO_VERTICAL_PIXELS, 0, VIDEO_HORIZONTAL_PIXELS, 0, 300);
181	GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
182
183	while (true) {
184#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
185		int available = blip_samples_avail(gba.audio.left);
186		if (available + audioBufferSize > SAMPLES) {
187			available = SAMPLES - audioBufferSize;
188		}
189		available &= ~((32 / sizeof(struct GBAStereoSample)) - 1); // Force align to 32 bytes
190		if (available > 0) {
191			blip_read_samples(gba.audio.left, &audioBuffer[currentAudioBuffer][audioBufferSize].left, available, true);
192			blip_read_samples(gba.audio.right, &audioBuffer[currentAudioBuffer][audioBufferSize].right, available, true);
193			audioBufferSize += available;
194		}
195		if (audioBufferSize == SAMPLES && !AUDIO_GetDMAEnableFlag()) {
196			_audioDMA();
197			AUDIO_StartDMA();
198		}
199#endif
200		PAD_ScanPads();
201		u16 padkeys = PAD_ButtonsHeld(0);
202		int keys = 0;
203		gba.keySource = &keys;
204		if (padkeys & PAD_BUTTON_A) {
205			keys |= 1 << GBA_KEY_A;
206		}
207		if (padkeys & PAD_BUTTON_B) {
208			keys |= 1 << GBA_KEY_B;
209		}
210		if (padkeys & PAD_TRIGGER_L) {
211			keys |= 1 << GBA_KEY_L;
212		}
213		if (padkeys & PAD_TRIGGER_R) {
214			keys |= 1 << GBA_KEY_R;
215		}
216		if (padkeys & PAD_BUTTON_START) {
217			keys |= 1 << GBA_KEY_START;
218		}
219		if (padkeys & (PAD_BUTTON_X | PAD_BUTTON_Y)) {
220			keys |= 1 << GBA_KEY_SELECT;
221		}
222		if (padkeys & PAD_BUTTON_LEFT) {
223			keys |= 1 << GBA_KEY_LEFT;
224		}
225		if (padkeys & PAD_BUTTON_RIGHT) {
226			keys |= 1 << GBA_KEY_RIGHT;
227		}
228		if (padkeys & PAD_BUTTON_UP) {
229			keys |= 1 << GBA_KEY_UP;
230		}
231		if (padkeys & PAD_BUTTON_DOWN) {
232			keys |= 1 << GBA_KEY_DOWN;
233		}
234		int x = PAD_StickX(0);
235		int y = PAD_StickY(0);
236		if (x < -0x40) {
237			keys |= 1 << GBA_KEY_LEFT;
238		}
239		if (x > 0x40) {
240			keys |= 1 << GBA_KEY_RIGHT;
241		}
242		if (y < -0x40) {
243			keys |= 1 << GBA_KEY_DOWN;
244		}
245		if (y > 0x40) {
246			keys |= 1 << GBA_KEY_UP;
247		}
248		if (padkeys & PAD_TRIGGER_Z) {
249			break;
250		}
251		int frameCount = gba.video.frameCounter;
252		while (gba.video.frameCounter == frameCount) {
253			ARMRunLoop(&cpu);
254		}
255	}
256
257	fclose(logfile);
258	free(fifo);
259
260	rom->close(rom);
261	save->close(save);
262
263	free(renderer.outputBuffer);
264	GUIFontDestroy(font);
265
266	return 0;
267}
268
269static void GBAWiiFrame(void) {
270	VIDEO_WaitVSync();
271
272	size_t x, y;
273	uint64_t* texdest = (uint64_t*) texmem;
274	uint64_t* texsrc = (uint64_t*) renderer.outputBuffer;
275	for (y = 0; y < VIDEO_VERTICAL_PIXELS; y += 4) {
276		for (x = 0; x < VIDEO_HORIZONTAL_PIXELS >> 2; ++x) {
277			texdest[0 + x * 4 + y * 64] = texsrc[0   + x + y * 64];
278			texdest[1 + x * 4 + y * 64] = texsrc[64  + x + y * 64];
279			texdest[2 + x * 4 + y * 64] = texsrc[128 + x + y * 64];
280			texdest[3 + x * 4 + y * 64] = texsrc[192 + x + y * 64];
281		}
282	}
283	DCFlushRange(texdest, 256 * 256 * BYTES_PER_PIXEL);
284
285	_drawStart();
286
287	GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_SET);
288	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
289	GX_InvalidateTexAll();
290	GX_LoadTexObj(&tex, GX_TEXMAP0);
291
292	GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
293	GX_Position2s16(0, 256);
294	GX_TexCoord2s16(0, 1);
295
296	GX_Position2s16(256, 256);
297	GX_TexCoord2s16(1, 1);
298
299	GX_Position2s16(256, 0);
300	GX_TexCoord2s16(1, 0);
301
302	GX_Position2s16(0, 0);
303	GX_TexCoord2s16(0, 0);
304	GX_End();
305
306	_drawEnd();
307}
308
309bool GBAWiiLoadGame(const char* path) {
310	_drawStart();
311	GUIFontPrintf(font, 0, 30, GUI_TEXT_CENTER, 0xFFFFFFFF, "Loading...");
312	_drawEnd();
313
314	rom = VFileOpen(path, O_RDONLY);
315
316	if (!rom) {
317		return false;
318	}
319	if (!GBAIsROM(rom)) {
320		return false;
321	}
322
323	save = VDirOptionalOpenFile(0, path, 0, ".sav", O_RDWR | O_CREAT);
324
325	GBALoadROM(&gba, rom, save, path);
326
327	struct GBACartridgeOverride override;
328	const struct GBACartridge* cart = (const struct GBACartridge*) gba.memory.rom;
329	memcpy(override.id, &cart->id, sizeof(override.id));
330	if (GBAOverrideFind(0, &override)) {
331		GBAOverrideApply(&gba, &override);
332	}
333
334	ARMReset(&cpu);
335	return true;
336}
337
338void GBAWiiLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) {
339	UNUSED(thread);
340	UNUSED(level);
341	vfprintf(logfile, format, args);
342	fprintf(logfile, "\n");
343	fflush(logfile);
344}
345
346static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer* renderer) {
347	UNUSED(stream);
348	UNUSED(renderer);
349	GBAWiiFrame();
350}
351
352static void _audioDMA(void) {
353	if (!audioBufferSize) {
354		return;
355	}
356	DCFlushRange(audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
357	AUDIO_InitDMA((u32) audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
358	currentAudioBuffer = (currentAudioBuffer + 1) % 3;
359	audioBufferSize = 0;
360}
361
362static void _drawStart(void) {
363	GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
364	GX_SetColorUpdate(GX_TRUE);
365
366	GX_SetViewport(0, 0, mode->fbWidth, mode->efbHeight, 0, 1);
367}
368
369static void _drawEnd(void) {
370	GX_DrawDone();
371
372	whichFb = !whichFb;
373
374	GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
375	VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
376	VIDEO_Flush();
377}
378
379static int _pollInput(void) {
380	PAD_ScanPads();
381	u16 padkeys = PAD_ButtonsHeld(0);
382	int keys = 0;
383	gba.keySource = &keys;
384	if (padkeys & PAD_BUTTON_A) {
385		keys |= 1 << GUI_INPUT_SELECT;
386	}
387	if (padkeys & PAD_BUTTON_B) {
388		keys |= 1 << GUI_INPUT_BACK;
389	}
390	if (padkeys & PAD_BUTTON_LEFT) {
391		keys |= 1 << GUI_INPUT_LEFT;
392	}
393	if (padkeys & PAD_BUTTON_RIGHT) {
394		keys |= 1 << GUI_INPUT_RIGHT;
395	}
396	if (padkeys & PAD_BUTTON_UP) {
397		keys |= 1 << GUI_INPUT_UP;
398	}
399	if (padkeys & PAD_BUTTON_DOWN) {
400		keys |= 1 << GUI_INPUT_DOWN;
401	}
402	return keys;
403}