all repos — mgba @ fec11012a5440b959149f39b7dde9f2af094c7b4

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