all repos — mgba @ 31f40b05ba0ca4be6bc0a1988ef81cac3e49307f

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#include <wiiuse/wpad.h>
 12
 13#include "util/common.h"
 14
 15#include "gba/renderers/video-software.h"
 16#include "gba/context/context.h"
 17#include "util/gui.h"
 18#include "util/gui/file-select.h"
 19#include "util/gui/font.h"
 20#include "util/vfs.h"
 21
 22#define SAMPLES 1024
 23
 24static void GBAWiiLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args);
 25static void GBAWiiFrame(void);
 26static bool GBAWiiLoadGame(const char* path);
 27
 28static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
 29static void _audioDMA(void);
 30static void _setRumble(struct GBARumble* rumble, int enable);
 31static void _sampleRotation(struct GBARotationSource* source);
 32static int32_t _readTiltX(struct GBARotationSource* source);
 33static int32_t _readTiltY(struct GBARotationSource* source);
 34static int32_t _readGyroZ(struct GBARotationSource* source);
 35
 36static void _drawStart(void);
 37static void _drawEnd(void);
 38static int _pollInput(void);
 39
 40static struct GBAContext context;
 41static struct GBAVideoSoftwareRenderer renderer;
 42static struct GBAAVStream stream;
 43static struct GBARumble rumble;
 44static struct GBARotationSource rotation;
 45static FILE* logfile;
 46static GXRModeObj* mode;
 47static Mtx model, view, modelview;
 48static uint16_t* texmem;
 49static GXTexObj tex;
 50static int32_t tiltX;
 51static int32_t tiltY;
 52static int32_t gyroZ;
 53
 54static void* framebuffer[2];
 55static int whichFb = 0;
 56
 57static struct GBAStereoSample audioBuffer[3][SAMPLES] __attribute__((__aligned__(32)));
 58static volatile size_t audioBufferSize = 0;
 59static volatile int currentAudioBuffer = 0;
 60
 61static struct GUIFont* font;
 62
 63int main() {
 64	VIDEO_Init();
 65	PAD_Init();
 66	WPAD_Init();
 67	AUDIO_Init(0);
 68	AUDIO_SetDSPSampleRate(AI_SAMPLERATE_48KHZ);
 69	AUDIO_RegisterDMACallback(_audioDMA);
 70
 71	memset(audioBuffer, 0, sizeof(audioBuffer));
 72
 73#if !defined(COLOR_16_BIT) && !defined(COLOR_5_6_5)
 74#error This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
 75#endif
 76
 77	mode = VIDEO_GetPreferredMode(0);
 78	framebuffer[0] = SYS_AllocateFramebuffer(mode);
 79	framebuffer[1] = SYS_AllocateFramebuffer(mode);
 80
 81	VIDEO_Configure(mode);
 82	VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
 83	VIDEO_SetBlack(FALSE);
 84	VIDEO_Flush();
 85	VIDEO_WaitVSync();
 86	if (mode->viTVMode & VI_NON_INTERLACE) {
 87		VIDEO_WaitVSync();
 88	}
 89
 90	GXColor bg = { 0, 0, 0, 0xFF };
 91	void* fifo = memalign(32, 0x40000);
 92	memset(fifo, 0, 0x40000);
 93	GX_Init(fifo, 0x40000);
 94	GX_SetCopyClear(bg, 0x00FFFFFF);
 95	GX_SetViewport(0, 0, mode->fbWidth, mode->efbHeight, 0, 1);
 96
 97	f32 yscale = GX_GetYScaleFactor(mode->efbHeight, mode->xfbHeight);
 98	u32 xfbHeight = GX_SetDispCopyYScale(yscale);
 99	GX_SetScissor(0, 0, mode->viWidth, mode->viWidth);
100	GX_SetDispCopySrc(0, 0, mode->fbWidth, mode->efbHeight);
101	GX_SetDispCopyDst(mode->fbWidth, xfbHeight);
102	GX_SetCopyFilter(mode->aa, mode->sample_pattern, GX_TRUE, mode->vfilter);
103	GX_SetFieldMode(mode->field_rendering, ((mode->viHeight == 2 * mode->xfbHeight) ? GX_ENABLE : GX_DISABLE));
104
105	GX_SetCullMode(GX_CULL_NONE);
106	GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
107	GX_SetDispCopyGamma(GX_GM_1_0);
108
109	GX_ClearVtxDesc();
110	GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
111	GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
112
113	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0);
114	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
115
116	GX_SetNumChans(1);
117	GX_SetNumTexGens(1);
118	GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
119	GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE);
120
121	GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
122	GX_InvVtxCache();
123	GX_InvalidateTexAll();
124
125	Mtx44 proj;
126
127	guVector cam = { 0.0f, 0.0f, 0.0f };
128	guVector up = { 0.0f, 1.0f, 0.0f };
129	guVector look = { 0.0f, 0.0f, -1.0f };
130	guLookAt(view, &cam, &up, &look);
131
132	guMtxIdentity(model);
133	guMtxTransApply(model, model, 0.0f, 0.0f, -6.0f);
134	guMtxConcat(view, model, modelview);
135	GX_LoadPosMtxImm(modelview, GX_PNMTX0);
136
137	texmem = memalign(32, 256 * 256 * BYTES_PER_PIXEL);
138	memset(texmem, 0, 256 * 256 * BYTES_PER_PIXEL);
139	GX_InitTexObj(&tex, texmem, 256, 256, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE);
140
141	font = GUIFontCreate();
142
143	fatInitDefault();
144
145	logfile = fopen("/mgba.log", "w");
146
147	stream.postAudioFrame = 0;
148	stream.postAudioBuffer = 0;
149	stream.postVideoFrame = _postVideoFrame;
150
151	rumble.setRumble = _setRumble;
152
153	rotation.sample = _sampleRotation;
154	rotation.readTiltX = _readTiltX;
155	rotation.readTiltY = _readTiltY;
156	rotation.readGyroZ = _readGyroZ;
157
158	GBAContextInit(&context, 0);
159	struct GBAOptions opts = {
160		.useBios = true,
161		.logLevel = 0,
162		.idleOptimization = IDLE_LOOP_DETECT
163	};
164	GBAConfigLoadDefaults(&context.config, &opts);
165	context.gba->logHandler = GBAWiiLog;
166	context.gba->stream = &stream;
167	context.gba->rumble = &rumble;
168	context.gba->rotationSource = &rotation;
169
170	GBAVideoSoftwareRendererCreate(&renderer);
171	renderer.outputBuffer = memalign(32, 256 * 256 * BYTES_PER_PIXEL);
172	renderer.outputBufferStride = 256;
173	context.renderer = &renderer.d;
174
175	GBAAudioResizeBuffer(&context.gba->audio, SAMPLES);
176
177#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
178	blip_set_rates(context.gba->audio.left,  GBA_ARM7TDMI_FREQUENCY, 48000);
179	blip_set_rates(context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 48000);
180#endif
181
182	struct GUIParams params = {
183		352, 230,
184		font, "/", _drawStart, _drawEnd, _pollInput,
185
186		GUI_PARAMS_TRAIL
187	};
188	GUIInit(&params);
189
190	while (true) {
191		char path[256];
192		guOrtho(proj, -20, 240, 0, 352, 0, 300);
193		GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
194		GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
195
196		if (!GUISelectFile(&params, path, sizeof(path), GBAIsROM) || !GBAWiiLoadGame(path)) {
197			break;
198		}
199		WPAD_SetDataFormat(0, WPAD_FMT_BTNS_ACC);
200		GBAContextStart(&context);
201		if (context.gba->memory.hw.devices & HW_GYRO) {
202			int i;
203			for (i = 0; i < 6; ++i) {
204				u32 result = WPAD_SetMotionPlus(0, 1);
205				if (result == WPAD_ERR_NONE) {
206					break;
207				}
208				sleep(1);
209			}
210		}
211
212		guOrtho(proj, -10, VIDEO_VERTICAL_PIXELS + 10, 0, VIDEO_HORIZONTAL_PIXELS, 0, 300);
213		GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
214		GX_SetVtxDesc(GX_VA_CLR0, GX_NONE);
215
216		while (true) {
217	#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
218			int available = blip_samples_avail(context.gba->audio.left);
219			if (available + audioBufferSize > SAMPLES) {
220				available = SAMPLES - audioBufferSize;
221			}
222			available &= ~((32 / sizeof(struct GBAStereoSample)) - 1); // Force align to 32 bytes
223			if (available > 0) {
224				blip_read_samples(context.gba->audio.left, &audioBuffer[currentAudioBuffer][audioBufferSize].left, available, true);
225				blip_read_samples(context.gba->audio.right, &audioBuffer[currentAudioBuffer][audioBufferSize].right, available, true);
226				audioBufferSize += available;
227			}
228			if (audioBufferSize == SAMPLES && !AUDIO_GetDMAEnableFlag()) {
229				_audioDMA();
230				AUDIO_StartDMA();
231			}
232	#endif
233			PAD_ScanPads();
234			u16 padkeys = PAD_ButtonsHeld(0);
235			WPAD_ScanPads();
236			u32 wiiPad = WPAD_ButtonsHeld(0);
237			u32 ext = 0;
238			uint16_t keys = 0;
239			WPAD_Probe(0, &ext);
240
241			if ((padkeys & PAD_BUTTON_A) || (wiiPad & WPAD_BUTTON_2) || 
242			    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_A | WPAD_CLASSIC_BUTTON_Y)))) {
243				keys |= 1 << GBA_KEY_A;
244			}
245			if ((padkeys & PAD_BUTTON_B) || (wiiPad & WPAD_BUTTON_1) ||
246			    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_B | WPAD_CLASSIC_BUTTON_X)))) {
247				keys |= 1 << GBA_KEY_B;
248			}
249			if ((padkeys & PAD_TRIGGER_L) || (wiiPad & WPAD_BUTTON_B) ||
250			    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_FULL_L))) {
251				keys |= 1 << GBA_KEY_L;
252			}
253			if ((padkeys & PAD_TRIGGER_R) || (wiiPad & WPAD_BUTTON_A) ||
254			    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_FULL_R))) {
255				keys |= 1 << GBA_KEY_R;
256			}
257			if ((padkeys & PAD_BUTTON_START) || (wiiPad & WPAD_BUTTON_PLUS) ||
258			    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_PLUS))) {
259				keys |= 1 << GBA_KEY_START;
260			}
261			if ((padkeys & (PAD_BUTTON_X | PAD_BUTTON_Y)) || (wiiPad & WPAD_BUTTON_MINUS) ||
262			    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_MINUS))) {
263				keys |= 1 << GBA_KEY_SELECT;
264			}
265			if ((padkeys & PAD_BUTTON_LEFT) || (wiiPad & WPAD_BUTTON_UP) ||
266			    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_LEFT))) {
267				keys |= 1 << GBA_KEY_LEFT;
268			}
269			if ((padkeys & PAD_BUTTON_RIGHT) || (wiiPad & WPAD_BUTTON_DOWN) ||
270			    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_RIGHT))) {
271				keys |= 1 << GBA_KEY_RIGHT;
272			}
273			if ((padkeys & PAD_BUTTON_UP) || (wiiPad & WPAD_BUTTON_RIGHT) ||
274			    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_UP))) {
275				keys |= 1 << GBA_KEY_UP;
276			}
277			if ((padkeys & PAD_BUTTON_DOWN) || (wiiPad & WPAD_BUTTON_LEFT) ||
278			    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_DOWN))) {
279				keys |= 1 << GBA_KEY_DOWN;
280			}
281			int x = PAD_StickX(0);
282			int y = PAD_StickY(0);
283			if (x < -0x40) {
284				keys |= 1 << GBA_KEY_LEFT;
285			}
286			if (x > 0x40) {
287				keys |= 1 << GBA_KEY_RIGHT;
288			}
289			if (y < -0x40) {
290				keys |= 1 << GBA_KEY_DOWN;
291			}
292			if (y > 0x40) {
293				keys |= 1 << GBA_KEY_UP;
294			}
295			if ((padkeys & PAD_TRIGGER_Z) || (wiiPad & WPAD_BUTTON_HOME) || (wiiPad & WPAD_CLASSIC_BUTTON_HOME)) {
296				break;
297			}
298			GBAContextFrame(&context, keys);
299		}
300		AUDIO_StopDMA();
301		GBAContextStop(&context);
302	}
303
304	fclose(logfile);
305	free(fifo);
306
307	GBAContextDeinit(&context);
308
309	free(renderer.outputBuffer);
310	GUIFontDestroy(font);
311
312	return 0;
313}
314
315static void GBAWiiFrame(void) {
316	size_t x, y;
317	uint64_t* texdest = (uint64_t*) texmem;
318	uint64_t* texsrc = (uint64_t*) renderer.outputBuffer;
319	for (y = 0; y < VIDEO_VERTICAL_PIXELS; y += 4) {
320		for (x = 0; x < VIDEO_HORIZONTAL_PIXELS >> 2; ++x) {
321			texdest[0 + x * 4 + y * 64] = texsrc[0   + x + y * 64];
322			texdest[1 + x * 4 + y * 64] = texsrc[64  + x + y * 64];
323			texdest[2 + x * 4 + y * 64] = texsrc[128 + x + y * 64];
324			texdest[3 + x * 4 + y * 64] = texsrc[192 + x + y * 64];
325		}
326	}
327	DCFlushRange(texdest, 256 * 256 * BYTES_PER_PIXEL);
328
329	_drawStart();
330
331	GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_SET);
332	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
333	GX_InvalidateTexAll();
334	GX_LoadTexObj(&tex, GX_TEXMAP0);
335
336	GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
337	GX_Position2s16(0, 256);
338	GX_TexCoord2s16(0, 1);
339
340	GX_Position2s16(256, 256);
341	GX_TexCoord2s16(1, 1);
342
343	GX_Position2s16(256, 0);
344	GX_TexCoord2s16(1, 0);
345
346	GX_Position2s16(0, 0);
347	GX_TexCoord2s16(0, 0);
348	GX_End();
349
350	_drawEnd();
351}
352
353bool GBAWiiLoadGame(const char* path) {
354	_drawStart();
355	GUIFontPrintf(font, 176, 120, GUI_TEXT_CENTER, 0xFFFFFFFF, "Loading...");
356	_drawEnd();
357	_drawStart();
358	GUIFontPrintf(font, 176, 120, GUI_TEXT_CENTER, 0xFFFFFFFF, "Loading...");
359	_drawEnd();
360
361	return GBAContextLoadROM(&context, path, true);
362}
363
364void GBAWiiLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) {
365	UNUSED(thread);
366	UNUSED(level);
367	if (!logfile) {
368		return;
369	}
370	vfprintf(logfile, format, args);
371	fprintf(logfile, "\n");
372	fflush(logfile);
373}
374
375static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer* renderer) {
376	UNUSED(stream);
377	UNUSED(renderer);
378	GBAWiiFrame();
379}
380
381static void _audioDMA(void) {
382	if (!audioBufferSize) {
383		return;
384	}
385	DCFlushRange(audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
386	AUDIO_InitDMA((u32) audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
387	currentAudioBuffer = (currentAudioBuffer + 1) % 3;
388	audioBufferSize = 0;
389}
390
391static void _drawStart(void) {
392	VIDEO_WaitVSync();
393	GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
394	GX_SetColorUpdate(GX_TRUE);
395
396	GX_SetViewport(0, 0, mode->fbWidth, mode->efbHeight, 0, 1);
397}
398
399static void _drawEnd(void) {
400	GX_DrawDone();
401
402	whichFb = !whichFb;
403
404	GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
405	VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
406	VIDEO_Flush();
407}
408
409static int _pollInput(void) {
410	PAD_ScanPads();
411	u16 padkeys = PAD_ButtonsHeld(0);
412
413	WPAD_ScanPads();
414	u32 wiiPad = WPAD_ButtonsHeld(0);
415	u32 ext = 0;
416	WPAD_Probe(0, &ext);
417
418	int keys = 0;
419	int x = PAD_StickX(0);
420	int y = PAD_StickY(0);
421	if (x < -0x40) {
422		keys |= 1 << GUI_INPUT_LEFT;
423	}
424	if (x > 0x40) {
425		keys |= 1 << GUI_INPUT_RIGHT;
426	}
427	if (y < -0x40) {
428		keys |= 1 << GUI_INPUT_DOWN;
429	}
430	if (y > 0x40) {
431		keys |= 1 << GUI_INPUT_UP;
432	}
433	if ((padkeys & PAD_BUTTON_A) || (wiiPad & WPAD_BUTTON_2) || 
434	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_A | WPAD_CLASSIC_BUTTON_Y)))) {
435		keys |= 1 << GUI_INPUT_SELECT;
436	}
437	if ((padkeys & PAD_BUTTON_B) || (wiiPad & WPAD_BUTTON_1) ||
438	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_B | WPAD_CLASSIC_BUTTON_X)))) {
439		keys |= 1 << GUI_INPUT_BACK;
440	}
441	if ((padkeys & PAD_TRIGGER_Z) || (wiiPad & WPAD_BUTTON_HOME) ||
442	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_HOME)))) {
443		keys |= 1 << GUI_INPUT_CANCEL;
444	}
445	if ((padkeys & PAD_BUTTON_LEFT)|| (wiiPad & WPAD_BUTTON_UP) ||
446	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_LEFT))) {
447		keys |= 1 << GUI_INPUT_LEFT;
448	}
449	if ((padkeys & PAD_BUTTON_RIGHT) || (wiiPad & WPAD_BUTTON_DOWN) ||
450	   ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_RIGHT))) {
451		keys |= 1 << GUI_INPUT_RIGHT;
452	}
453	if ((padkeys & PAD_BUTTON_UP) || (wiiPad & WPAD_BUTTON_RIGHT) ||
454	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_UP))) {
455		keys |= 1 << GUI_INPUT_UP;
456	}
457	if ((padkeys & PAD_BUTTON_DOWN) || (wiiPad & WPAD_BUTTON_LEFT) ||
458	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_DOWN))) {
459		keys |= 1 << GUI_INPUT_DOWN;
460	}
461	return keys;
462}
463
464void _setRumble(struct GBARumble* rumble, int enable) {
465	UNUSED(rumble);
466	WPAD_Rumble(0, enable);
467	if (enable) {
468		PAD_ControlMotor(0, PAD_MOTOR_RUMBLE);
469	} else {
470		PAD_ControlMotor(0, PAD_MOTOR_STOP);
471	}
472}
473
474void _sampleRotation(struct GBARotationSource* source) {
475	UNUSED(source);
476	vec3w_t accel;
477	WPAD_Accel(0, &accel);
478	// These are swapped
479	tiltX = (accel.y - 0x1EA) << 22;
480	tiltY = (accel.x - 0x1EA) << 22;
481
482	// This doesn't seem to work at all with -TR remotes
483	struct expansion_t exp;
484	WPAD_Expansion(0, &exp);
485	if (exp.type != EXP_MOTION_PLUS) {
486		return;
487	}
488	gyroZ = exp.mp.rz - 0x1FA0;
489	gyroZ <<= 18;
490}
491
492int32_t _readTiltX(struct GBARotationSource* source) {
493	UNUSED(source);
494	return tiltX;
495}
496
497int32_t _readTiltY(struct GBARotationSource* source) {
498	UNUSED(source);
499	return tiltY;
500}
501
502int32_t _readGyroZ(struct GBARotationSource* source) {
503	UNUSED(source);
504	return gyroZ;
505}