all repos — mgba @ 92c6b90b03f8c04b53312bf2273d86a9783ee073

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 <ogc/machine/processor.h>
 11#include <malloc.h>
 12#include <unistd.h>
 13#include <wiiuse/wpad.h>
 14
 15#include "util/common.h"
 16
 17#include "gba/renderers/video-software.h"
 18#include "gba/context/context.h"
 19#include "gba/gui/gui-runner.h"
 20#include "util/gui.h"
 21#include "util/gui/file-select.h"
 22#include "util/gui/font.h"
 23#include "util/gui/menu.h"
 24#include "util/vfs.h"
 25
 26#define GCN1_INPUT 0x47434E31
 27#define GCN2_INPUT 0x47434E32
 28#define WIIMOTE_INPUT 0x5749494D
 29#define CLASSIC_INPUT 0x57494943
 30
 31static void _mapKey(struct mInputMap* map, uint32_t binding, int nativeKey, enum GBAKey key) {
 32	mInputBindKey(map, binding, __builtin_ctz(nativeKey), key);
 33}
 34
 35static enum ScreenMode {
 36	SM_PA,
 37	SM_SF,
 38	SM_MAX
 39} screenMode = SM_PA;
 40
 41enum FilterMode {
 42	FM_NEAREST,
 43	FM_LINEAR,
 44	FM_MAX
 45};
 46
 47#define SAMPLES 1024
 48#define GUI_SCALE 1.35
 49
 50static void _retraceCallback(u32 count);
 51
 52static void _audioDMA(void);
 53static void _setRumble(struct GBARumble* rumble, int enable);
 54static void _sampleRotation(struct mRotationSource* source);
 55static int32_t _readTiltX(struct mRotationSource* source);
 56static int32_t _readTiltY(struct mRotationSource* source);
 57static int32_t _readGyroZ(struct mRotationSource* source);
 58
 59static void _drawStart(void);
 60static void _drawEnd(void);
 61static uint32_t _pollInput(void);
 62static enum GUICursorState _pollCursor(unsigned* x, unsigned* y);
 63static void _guiPrepare(void);
 64static void _guiFinish(void);
 65
 66static void _setup(struct GBAGUIRunner* runner);
 67static void _gameLoaded(struct GBAGUIRunner* runner);
 68static void _gameUnloaded(struct GBAGUIRunner* runner);
 69static void _unpaused(struct GBAGUIRunner* runner);
 70static void _drawFrame(struct GBAGUIRunner* runner, bool faded);
 71static uint16_t _pollGameInput(struct GBAGUIRunner* runner);
 72
 73static s8 WPAD_StickX(u8 chan, u8 right);
 74static s8 WPAD_StickY(u8 chan, u8 right);
 75
 76static struct GBAVideoSoftwareRenderer renderer;
 77static struct GBARumble rumble;
 78static struct mRotationSource rotation;
 79static GXRModeObj* vmode;
 80static Mtx model, view, modelview;
 81static uint16_t* texmem;
 82static GXTexObj tex;
 83static int32_t tiltX;
 84static int32_t tiltY;
 85static int32_t gyroZ;
 86static uint32_t retraceCount;
 87static uint32_t referenceRetraceCount;
 88static int scaleFactor;
 89
 90static void* framebuffer[2] = { 0, 0 };
 91static int whichFb = 0;
 92
 93static struct GBAStereoSample audioBuffer[3][SAMPLES] __attribute__((__aligned__(32)));
 94static volatile size_t audioBufferSize = 0;
 95static volatile int currentAudioBuffer = 0;
 96
 97static struct GUIFont* font;
 98
 99static void reconfigureScreen(GXRModeObj* vmode) {
100	free(framebuffer[0]);
101	free(framebuffer[1]);
102
103	framebuffer[0] = SYS_AllocateFramebuffer(vmode);
104	framebuffer[1] = SYS_AllocateFramebuffer(vmode);
105
106	VIDEO_SetBlack(true);
107	VIDEO_Configure(vmode);
108	VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
109	VIDEO_SetBlack(false);
110	VIDEO_Flush();
111	VIDEO_WaitVSync();
112	if (vmode->viTVMode & VI_NON_INTERLACE) {
113		VIDEO_WaitVSync();
114	}
115	GX_SetViewport(0, 0, vmode->fbWidth, vmode->efbHeight, 0, 1);
116
117	f32 yscale = GX_GetYScaleFactor(vmode->efbHeight, vmode->xfbHeight);
118	u32 xfbHeight = GX_SetDispCopyYScale(yscale);
119	GX_SetScissor(0, 0, vmode->viWidth, vmode->viWidth);
120	GX_SetDispCopySrc(0, 0, vmode->fbWidth, vmode->efbHeight);
121	GX_SetDispCopyDst(vmode->fbWidth, xfbHeight);
122	GX_SetCopyFilter(vmode->aa, vmode->sample_pattern, GX_TRUE, vmode->vfilter);
123	GX_SetFieldMode(vmode->field_rendering, ((vmode->viHeight == 2 * vmode->xfbHeight) ? GX_ENABLE : GX_DISABLE));
124
125	int hfactor = vmode->fbWidth / VIDEO_HORIZONTAL_PIXELS;
126	int vfactor = vmode->efbHeight / VIDEO_VERTICAL_PIXELS;
127	if (hfactor > vfactor) {
128		scaleFactor = vfactor;
129	} else {
130		scaleFactor = hfactor;
131	}
132};
133
134int main(int argc, char* argv[]) {
135	VIDEO_Init();
136	PAD_Init();
137	WPAD_Init();
138	WPAD_SetDataFormat(0, WPAD_FMT_BTNS_ACC_IR);
139	AUDIO_Init(0);
140	AUDIO_SetDSPSampleRate(AI_SAMPLERATE_48KHZ);
141	AUDIO_RegisterDMACallback(_audioDMA);
142
143	memset(audioBuffer, 0, sizeof(audioBuffer));
144
145#if !defined(COLOR_16_BIT) && !defined(COLOR_5_6_5)
146#error This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
147#endif
148
149	vmode = VIDEO_GetPreferredMode(0);
150
151	GXColor bg = { 0, 0, 0, 0xFF };
152	void* fifo = memalign(32, 0x40000);
153	memset(fifo, 0, 0x40000);
154	GX_Init(fifo, 0x40000);
155	GX_SetCopyClear(bg, 0x00FFFFFF);
156
157	reconfigureScreen(vmode);
158
159	GX_SetCullMode(GX_CULL_NONE);
160	GX_SetDispCopyGamma(GX_GM_1_0);
161
162	GX_ClearVtxDesc();
163	GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
164	GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
165	GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
166
167	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0);
168	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
169	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
170
171	GX_SetNumChans(1);
172	GX_SetNumTexGens(1);
173	GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
174	GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE);
175
176	GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
177	GX_InvVtxCache();
178	GX_InvalidateTexAll();
179
180	guVector cam = { 0.0f, 0.0f, 0.0f };
181	guVector up = { 0.0f, 1.0f, 0.0f };
182	guVector look = { 0.0f, 0.0f, -1.0f };
183	guLookAt(view, &cam, &up, &look);
184
185	guMtxIdentity(model);
186	guMtxTransApply(model, model, 0.0f, 0.0f, -6.0f);
187	guMtxConcat(view, model, modelview);
188	GX_LoadPosMtxImm(modelview, GX_PNMTX0);
189
190	texmem = memalign(32, 256 * 256 * BYTES_PER_PIXEL);
191	memset(texmem, 0, 256 * 256 * BYTES_PER_PIXEL);
192	GX_InitTexObj(&tex, texmem, 256, 256, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE);
193
194	VIDEO_SetPostRetraceCallback(_retraceCallback);
195
196	font = GUIFontCreate();
197
198	fatInitDefault();
199
200	rumble.setRumble = _setRumble;
201
202	rotation.sample = _sampleRotation;
203	rotation.readTiltX = _readTiltX;
204	rotation.readTiltY = _readTiltY;
205	rotation.readGyroZ = _readGyroZ;
206
207	struct GBAGUIRunner runner = {
208		.params = {
209			vmode->fbWidth * GUI_SCALE, vmode->efbHeight * GUI_SCALE,
210			font, "",
211			_drawStart, _drawEnd,
212			_pollInput, _pollCursor,
213			0,
214			_guiPrepare, _guiFinish,
215
216			GUI_PARAMS_TRAIL
217		},
218		.keySources = (struct GUIInputKeys[]) {
219			{
220				.name = "GameCube Input (1)",
221				.id = GCN1_INPUT,
222				.keyNames = (const char*[]) {
223					"D-Pad Left",
224					"D-Pad Right",
225					"D-Pad Down",
226					"D-Pad Up",
227					"Z",
228					"R",
229					"L",
230					0,
231					"A",
232					"B",
233					"X",
234					"Y",
235					"Start"
236				},
237				.nKeys = 13
238			},
239			{
240				.name = "GameCube Input (2)",
241				.id = GCN2_INPUT,
242				.keyNames = (const char*[]) {
243					"D-Pad Left",
244					"D-Pad Right",
245					"D-Pad Down",
246					"D-Pad Up",
247					"Z",
248					"R",
249					"L",
250					0,
251					"A",
252					"B",
253					"X",
254					"Y",
255					"Start"
256				},
257				.nKeys = 13
258			},
259			{
260				.name = "Wii Remote Input",
261				.id = WIIMOTE_INPUT,
262				.keyNames = (const char*[]) {
263					"2",
264					"1",
265					"B",
266					"A",
267					"-",
268					0,
269					0,
270					"\1\xE",
271					"Left",
272					"Right",
273					"Down",
274					"Up",
275					"+",
276					0,
277					0,
278					0,
279					"Z",
280					"C",
281				},
282				.nKeys = 18
283			},
284			{
285				.name = "Classic Controller Input",
286				.id = CLASSIC_INPUT,
287				.keyNames = (const char*[]) {
288					0,
289					0,
290					0,
291					0,
292					0,
293					0,
294					0,
295					0,
296					0,
297					0,
298					0,
299					0,
300					0,
301					0,
302					0,
303					0,
304					"Up",
305					"Left",
306					"ZR",
307					"X",
308					"A",
309					"Y",
310					"B",
311					"ZL",
312					0,
313					"R",
314					"+",
315					"\1\xE",
316					"-",
317					"L",
318					"Down",
319					"Right",
320				},
321				.nKeys = 32
322			},
323			{ .id = 0 }
324		},
325		.configExtra = (struct GUIMenuItem[]) {
326			{
327				.title = "Screen mode",
328				.data = "screenMode",
329				.submenu = 0,
330				.state = 0,
331				.validStates = (const char*[]) {
332					"Pixel-Accurate",
333					"Stretched",
334				},
335				.nStates = 2
336			},
337			{
338				.title = "Filtering",
339				.data = "filter",
340				.submenu = 0,
341				.state = 0,
342				.validStates = (const char*[]) {
343					"Pixelated",
344					"Resampled",
345				},
346				.nStates = 2
347			}
348		},
349		.nConfigExtra = 2,
350		.setup = _setup,
351		.teardown = 0,
352		.gameLoaded = _gameLoaded,
353		.gameUnloaded = _gameUnloaded,
354		.prepareForFrame = 0,
355		.drawFrame = _drawFrame,
356		.paused = _gameUnloaded,
357		.unpaused = _unpaused,
358		.pollGameInput = _pollGameInput
359	};
360	GBAGUIInit(&runner, "wii");
361	if (argc > 1) {
362		GBAGUIRun(&runner, argv[1]);
363	} else {
364		GBAGUIRunloop(&runner);
365	}
366	GBAGUIDeinit(&runner);
367
368	free(fifo);
369
370	free(renderer.outputBuffer);
371	GUIFontDestroy(font);
372
373	free(framebuffer[0]);
374	free(framebuffer[1]);
375
376	return 0;
377}
378
379static void _audioDMA(void) {
380	if (!audioBufferSize) {
381		return;
382	}
383	DCFlushRange(audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
384	AUDIO_InitDMA((u32) audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
385	currentAudioBuffer = (currentAudioBuffer + 1) % 3;
386	audioBufferSize = 0;
387}
388
389static void _drawStart(void) {
390	u32 level = 0;
391	_CPU_ISR_Disable(level);
392	if (referenceRetraceCount >= retraceCount) {
393		VIDEO_WaitVSync();
394	}
395	_CPU_ISR_Restore(level);
396
397	GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
398	GX_SetColorUpdate(GX_TRUE);
399
400	GX_SetViewport(0, 0, vmode->fbWidth, vmode->efbHeight, 0, 1);
401}
402
403static void _drawEnd(void) {
404	whichFb = !whichFb;
405
406	GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
407	GX_DrawDone();
408	VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
409	VIDEO_Flush();
410
411	u32 level = 0;
412	_CPU_ISR_Disable(level);
413	++referenceRetraceCount;
414	_CPU_ISR_Restore(level);
415}
416
417static uint32_t _pollInput(void) {
418	PAD_ScanPads();
419	u16 padkeys = PAD_ButtonsHeld(0);
420
421	WPAD_ScanPads();
422	u32 wiiPad = WPAD_ButtonsHeld(0);
423	u32 ext = 0;
424	WPAD_Probe(0, &ext);
425
426	int keys = 0;
427	int x = PAD_StickX(0);
428	int y = PAD_StickY(0);
429	int w_x = WPAD_StickX(0, 0);
430	int w_y = WPAD_StickY(0, 0);
431	if (x < -0x20 || w_x < -0x20) {
432		keys |= 1 << GUI_INPUT_LEFT;
433	}
434	if (x > 0x20 || w_x > 0x20) {
435		keys |= 1 << GUI_INPUT_RIGHT;
436	}
437	if (y < -0x20 || w_y <- 0x20) {
438		keys |= 1 << GUI_INPUT_DOWN;
439	}
440	if (y > 0x20 || w_y > 0x20) {
441		keys |= 1 << GUI_INPUT_UP;
442	}
443	if ((padkeys & PAD_BUTTON_A) || (wiiPad & WPAD_BUTTON_2) || 
444	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_A | WPAD_CLASSIC_BUTTON_Y)))) {
445		keys |= 1 << GUI_INPUT_SELECT;
446	}
447	if ((padkeys & PAD_BUTTON_B) || (wiiPad & WPAD_BUTTON_1) || (wiiPad & WPAD_BUTTON_B) ||
448	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_B | WPAD_CLASSIC_BUTTON_X)))) {
449		keys |= 1 << GUI_INPUT_BACK;
450	}
451	if ((padkeys & PAD_TRIGGER_Z) || (wiiPad & WPAD_BUTTON_HOME) ||
452	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_HOME)))) {
453		keys |= 1 << GUI_INPUT_CANCEL;
454	}
455	if ((padkeys & PAD_BUTTON_LEFT)|| (wiiPad & WPAD_BUTTON_UP) ||
456	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_LEFT))) {
457		keys |= 1 << GUI_INPUT_LEFT;
458	}
459	if ((padkeys & PAD_BUTTON_RIGHT) || (wiiPad & WPAD_BUTTON_DOWN) ||
460	   ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_RIGHT))) {
461		keys |= 1 << GUI_INPUT_RIGHT;
462	}
463	if ((padkeys & PAD_BUTTON_UP) || (wiiPad & WPAD_BUTTON_RIGHT) ||
464	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_UP))) {
465		keys |= 1 << GUI_INPUT_UP;
466	}
467	if ((padkeys & PAD_BUTTON_DOWN) || (wiiPad & WPAD_BUTTON_LEFT) ||
468	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_DOWN))) {
469		keys |= 1 << GUI_INPUT_DOWN;
470	}
471	return keys;
472}
473
474static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
475	ir_t ir;
476	WPAD_IR(0, &ir);
477	if (!ir.smooth_valid) {
478		return GUI_CURSOR_NOT_PRESENT;
479	}
480	*x = ir.sx;
481	*y = ir.sy;
482	WPAD_ScanPads();
483	u32 wiiPad = WPAD_ButtonsHeld(0);
484	if (wiiPad & WPAD_BUTTON_A) {
485		return GUI_CURSOR_DOWN;
486	}
487	return GUI_CURSOR_UP;
488}
489
490void _reproj(int w, int h) {
491	Mtx44 proj;
492	int top = (vmode->efbHeight - h) / 2;
493	int left = (vmode->fbWidth - w) / 2;
494	guOrtho(proj, -top, top + h, -left, left + w, 0, 300);
495	GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
496}
497
498void _reproj2(int w, int h) {
499	Mtx44 proj;
500	s16 top = 20;
501	guOrtho(proj, -top, top + h, 0, w, 0, 300);
502	GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
503}
504
505void _guiPrepare(void) {
506	_reproj2(vmode->fbWidth * GUI_SCALE, vmode->efbHeight * GUI_SCALE);
507}
508
509void _guiFinish(void) {
510	if (screenMode == SM_PA) {
511		_reproj(VIDEO_HORIZONTAL_PIXELS * scaleFactor, VIDEO_VERTICAL_PIXELS * scaleFactor);
512	} else {
513		_reproj2(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
514	}
515}
516
517void _setup(struct GBAGUIRunner* runner) {
518	runner->context.gba->rumble = &rumble;
519	runner->context.gba->rotationSource = &rotation;
520
521	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_BUTTON_A, GBA_KEY_A);
522	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_BUTTON_B, GBA_KEY_B);
523	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_BUTTON_START, GBA_KEY_START);
524	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_BUTTON_X, GBA_KEY_SELECT);
525	_mapKey(&runner->context.inputMap, GCN2_INPUT, PAD_BUTTON_Y, GBA_KEY_SELECT);
526	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_BUTTON_UP, GBA_KEY_UP);
527	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_BUTTON_DOWN, GBA_KEY_DOWN);
528	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_BUTTON_LEFT, GBA_KEY_LEFT);
529	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_BUTTON_RIGHT, GBA_KEY_RIGHT);
530	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_TRIGGER_L, GBA_KEY_L);
531	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_TRIGGER_R, GBA_KEY_R);
532
533	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_2, GBA_KEY_A);
534	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_1, GBA_KEY_B);
535	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_PLUS, GBA_KEY_START);
536	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_MINUS, GBA_KEY_SELECT);
537	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_RIGHT, GBA_KEY_UP);
538	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_LEFT, GBA_KEY_DOWN);
539	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_UP, GBA_KEY_LEFT);
540	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_DOWN, GBA_KEY_RIGHT);
541	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_B, GBA_KEY_L);
542	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_A, GBA_KEY_R);
543
544	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_A, GBA_KEY_A);
545	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_B, GBA_KEY_B);
546	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_PLUS, GBA_KEY_START);
547	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_MINUS, GBA_KEY_SELECT);
548	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_UP, GBA_KEY_UP);
549	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_DOWN, GBA_KEY_DOWN);
550	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_LEFT, GBA_KEY_LEFT);
551	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_RIGHT, GBA_KEY_RIGHT);
552	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_FULL_L, GBA_KEY_L);
553	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_FULL_R, GBA_KEY_R);
554
555	struct mInputAxis desc = { GBA_KEY_RIGHT, GBA_KEY_LEFT, 0x20, -0x20 };
556	mInputBindAxis(&runner->context.inputMap, GCN1_INPUT, 0, &desc);
557	mInputBindAxis(&runner->context.inputMap, CLASSIC_INPUT, 0, &desc);
558	desc = (struct mInputAxis) { GBA_KEY_UP, GBA_KEY_DOWN, 0x20, -0x20 };
559	mInputBindAxis(&runner->context.inputMap, GCN1_INPUT, 1, &desc);
560	mInputBindAxis(&runner->context.inputMap, CLASSIC_INPUT, 1, &desc);
561
562	GBAVideoSoftwareRendererCreate(&renderer);
563	renderer.outputBuffer = memalign(32, 256 * 256 * BYTES_PER_PIXEL);
564	renderer.outputBufferStride = 256;
565	runner->context.renderer = &renderer.d;
566
567	GBAAudioResizeBuffer(&runner->context.gba->audio, SAMPLES);
568
569	double ratio = GBAAudioCalculateRatio(1, 60 / 1.001, 1);
570	blip_set_rates(runner->context.gba->audio.psg.left,  GBA_ARM7TDMI_FREQUENCY, 48000 * ratio);
571	blip_set_rates(runner->context.gba->audio.psg.right, GBA_ARM7TDMI_FREQUENCY, 48000 * ratio);
572}
573
574void _gameUnloaded(struct GBAGUIRunner* runner) {
575	UNUSED(runner);
576	AUDIO_StopDMA();
577}
578
579void _gameLoaded(struct GBAGUIRunner* runner) {
580	if (runner->context.gba->memory.hw.devices & HW_GYRO) {
581		int i;
582		for (i = 0; i < 6; ++i) {
583			u32 result = WPAD_SetMotionPlus(0, 1);
584			if (result == WPAD_ERR_NONE) {
585				break;
586			}
587			sleep(1);
588		}
589	}
590	_unpaused(runner);
591}
592
593void _unpaused(struct GBAGUIRunner* runner) {
594	u32 level = 0;
595	_CPU_ISR_Disable(level);
596	referenceRetraceCount = retraceCount;
597	_CPU_ISR_Restore(level);
598
599	unsigned mode;
600	if (mCoreConfigGetUIntValue(&runner->context.config, "screenMode", &mode) && mode < SM_MAX) {
601		screenMode = mode;
602	}
603	if (mCoreConfigGetUIntValue(&runner->context.config, "filter", &mode) && mode < FM_MAX) {
604		switch (mode) {
605		case FM_NEAREST:
606		default:
607			GX_InitTexObjFilterMode(&tex, GX_NEAR, GX_NEAR);
608			break;
609		case FM_LINEAR:
610			GX_InitTexObjFilterMode(&tex, GX_LINEAR, GX_LINEAR);
611			break;
612		}
613	}
614	_guiFinish();
615}
616
617void _drawFrame(struct GBAGUIRunner* runner, bool faded) {
618	int available = blip_samples_avail(runner->context.gba->audio.psg.left);
619	if (available + audioBufferSize > SAMPLES) {
620		available = SAMPLES - audioBufferSize;
621	}
622	available &= ~((32 / sizeof(struct GBAStereoSample)) - 1); // Force align to 32 bytes
623	if (available > 0) {
624		// These appear to be reversed for AUDIO_InitDMA
625		blip_read_samples(runner->context.gba->audio.psg.left, &audioBuffer[currentAudioBuffer][audioBufferSize].right, available, true);
626		blip_read_samples(runner->context.gba->audio.psg.right, &audioBuffer[currentAudioBuffer][audioBufferSize].left, available, true);
627		audioBufferSize += available;
628	}
629	if (audioBufferSize == SAMPLES && !AUDIO_GetDMAEnableFlag()) {
630		_audioDMA();
631		AUDIO_StartDMA();
632	}
633
634	uint32_t color = 0xFFFFFF3F;
635	if (!faded) {
636		color |= 0xC0;
637	}
638	size_t x, y;
639	uint64_t* texdest = (uint64_t*) texmem;
640	uint64_t* texsrc = (uint64_t*) renderer.outputBuffer;
641	for (y = 0; y < VIDEO_VERTICAL_PIXELS; y += 4) {
642		for (x = 0; x < VIDEO_HORIZONTAL_PIXELS >> 2; ++x) {
643			texdest[0 + x * 4 + y * 64] = texsrc[0   + x + y * 64];
644			texdest[1 + x * 4 + y * 64] = texsrc[64  + x + y * 64];
645			texdest[2 + x * 4 + y * 64] = texsrc[128 + x + y * 64];
646			texdest[3 + x * 4 + y * 64] = texsrc[192 + x + y * 64];
647		}
648	}
649	DCFlushRange(texdest, 256 * 256 * BYTES_PER_PIXEL);
650
651	if (faded) {
652		GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_NOOP);
653	} else {
654		GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_NOOP);
655	}
656	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
657	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
658	GX_InvalidateTexAll();
659	GX_LoadTexObj(&tex, GX_TEXMAP0);
660
661	s16 vertSize = 256;
662	if (screenMode == SM_PA) {
663		vertSize *= scaleFactor;
664	}
665
666	GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
667	GX_Position2s16(0, vertSize);
668	GX_Color1u32(color);
669	GX_TexCoord2s16(0, 1);
670
671	GX_Position2s16(vertSize, vertSize);
672	GX_Color1u32(color);
673	GX_TexCoord2s16(1, 1);
674
675	GX_Position2s16(vertSize, 0);
676	GX_Color1u32(color);
677	GX_TexCoord2s16(1, 0);
678
679	GX_Position2s16(0, 0);
680	GX_Color1u32(color);
681	GX_TexCoord2s16(0, 0);
682	GX_End();
683}
684
685uint16_t _pollGameInput(struct GBAGUIRunner* runner) {
686	UNUSED(runner);
687	PAD_ScanPads();
688	u16 padkeys = PAD_ButtonsHeld(0);
689	WPAD_ScanPads();
690	u32 wiiPad = WPAD_ButtonsHeld(0);
691	u32 ext = 0;
692	WPAD_Probe(0, &ext);
693	uint16_t keys = mInputMapKeyBits(&runner->context.inputMap, GCN1_INPUT, padkeys, 0);
694	keys |= mInputMapKeyBits(&runner->context.inputMap, GCN2_INPUT, padkeys, 0);
695	keys |= mInputMapKeyBits(&runner->context.inputMap, WIIMOTE_INPUT, wiiPad, 0);
696
697	enum GBAKey angles = mInputMapAxis(&runner->context.inputMap, GCN1_INPUT, 0, PAD_StickX(0));
698	if (angles != GBA_KEY_NONE) {
699		keys |= 1 << angles;
700	}
701	angles = mInputMapAxis(&runner->context.inputMap, GCN1_INPUT, 1, PAD_StickY(0));
702	if (angles != GBA_KEY_NONE) {
703		keys |= 1 << angles;
704	}
705	if (ext == WPAD_EXP_CLASSIC) {
706		keys |= mInputMapKeyBits(&runner->context.inputMap, CLASSIC_INPUT, wiiPad, 0);
707		angles = mInputMapAxis(&runner->context.inputMap, CLASSIC_INPUT, 0, WPAD_StickX(0, 0));
708		if (angles != GBA_KEY_NONE) {
709			keys |= 1 << angles;
710		}
711		angles = mInputMapAxis(&runner->context.inputMap, CLASSIC_INPUT, 1, WPAD_StickY(0, 0));
712		if (angles != GBA_KEY_NONE) {
713			keys |= 1 << angles;
714		}
715	}
716
717	return keys;
718}
719
720void _setRumble(struct GBARumble* rumble, int enable) {
721	UNUSED(rumble);
722	WPAD_Rumble(0, enable);
723	if (enable) {
724		PAD_ControlMotor(0, PAD_MOTOR_RUMBLE);
725	} else {
726		PAD_ControlMotor(0, PAD_MOTOR_STOP);
727	}
728}
729
730void _sampleRotation(struct mRotationSource* source) {
731	UNUSED(source);
732	vec3w_t accel;
733	WPAD_Accel(0, &accel);
734	// These are swapped
735	tiltX = (accel.y - 0x1EA) << 22;
736	tiltY = (accel.x - 0x1EA) << 22;
737
738	// This doesn't seem to work at all with -TR remotes
739	struct expansion_t exp;
740	WPAD_Expansion(0, &exp);
741	if (exp.type != EXP_MOTION_PLUS) {
742		return;
743	}
744	gyroZ = exp.mp.rz - 0x1FA0;
745	gyroZ <<= 18;
746}
747
748int32_t _readTiltX(struct mRotationSource* source) {
749	UNUSED(source);
750	return tiltX;
751}
752
753int32_t _readTiltY(struct mRotationSource* source) {
754	UNUSED(source);
755	return tiltY;
756}
757
758int32_t _readGyroZ(struct mRotationSource* source) {
759	UNUSED(source);
760	return gyroZ;
761}
762
763static s8 WPAD_StickX(u8 chan, u8 right) {
764	float mag = 0.0;
765	float ang = 0.0;
766	WPADData *data = WPAD_Data(chan);
767
768	switch (data->exp.type)	{
769	case WPAD_EXP_NUNCHUK:
770	case WPAD_EXP_GUITARHERO3:
771		if (right == 0) {
772			mag = data->exp.nunchuk.js.mag;
773			ang = data->exp.nunchuk.js.ang;
774		}
775		break;
776	case WPAD_EXP_CLASSIC:
777		if (right == 0) {
778			mag = data->exp.classic.ljs.mag;
779			ang = data->exp.classic.ljs.ang;
780		} else {
781			mag = data->exp.classic.rjs.mag;
782			ang = data->exp.classic.rjs.ang;
783		}
784		break;
785	default:
786		break;
787	}
788
789	/* calculate X value (angle need to be converted into radian) */
790	if (mag > 1.0) {
791		mag = 1.0;
792	} else if (mag < -1.0) {
793		mag = -1.0;
794	}
795	double val = mag * sinf(M_PI * ang / 180.0f);
796 
797	return (s8)(val * 128.0f);
798}
799
800static s8 WPAD_StickY(u8 chan, u8 right) {
801	float mag = 0.0;
802	float ang = 0.0;
803	WPADData *data = WPAD_Data(chan);
804
805	switch (data->exp.type) {
806	case WPAD_EXP_NUNCHUK:
807	case WPAD_EXP_GUITARHERO3:
808		if (right == 0) {
809			mag = data->exp.nunchuk.js.mag;
810			ang = data->exp.nunchuk.js.ang;
811		}
812		break;
813	case WPAD_EXP_CLASSIC:
814		if (right == 0) {
815			mag = data->exp.classic.ljs.mag;
816			ang = data->exp.classic.ljs.ang;
817		} else {
818			mag = data->exp.classic.rjs.mag;
819			ang = data->exp.classic.rjs.ang;
820		}
821		break;
822	default:
823		break;
824	}
825
826	/* calculate X value (angle need to be converted into radian) */
827	if (mag > 1.0) { 
828		mag = 1.0;
829	} else if (mag < -1.0) {
830		mag = -1.0;
831	}
832	double val = mag * cosf(M_PI * ang / 180.0f);
833 
834	return (s8)(val * 128.0f);
835}
836
837void _retraceCallback(u32 count) {
838	u32 level = 0;
839	_CPU_ISR_Disable(level);
840	retraceCount = count;
841	_CPU_ISR_Restore(level);
842}